Skip to content

Commit da94979

Browse files
georgeglarsonclaude
andcommitted
Add request logging and replace excessive emdashes across UI
Add morgan for request logging so we can see when reviewers visit. Replace emdash-heavy prose with periods, commas, and colons for cleaner, more natural copy throughout all exercise panels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b8767d0 commit da94979

13 files changed

Lines changed: 123 additions & 39 deletions

package-lock.json

Lines changed: 84 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
"test:watch": "vitest --reporter=verbose"
2020
},
2121
"dependencies": {
22+
"@types/morgan": "^1.9.10",
2223
"better-sqlite3": "^11.7.0",
2324
"cors": "^2.8.5",
2425
"express": "^4.21.0",
2526
"express-rate-limit": "^8.2.1",
26-
"helmet": "^8.1.0"
27+
"helmet": "^8.1.0",
28+
"morgan": "^1.10.1"
2729
},
2830
"devDependencies": {
2931
"@eslint/js": "^10.0.1",

src/client/__tests__/panels.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,9 +1014,9 @@ describe("OnScreenKeyboardPanel — visualizer controls", () => {
10141014
expect(screen.getByText("0/11")).toBeInTheDocument();
10151015
});
10161016

1017-
it("move indicator shows 'Cursor at A ready' at start", async () => {
1017+
it("move indicator shows 'Cursor at A, ready' at start", async () => {
10181018
await runSpellIt();
1019-
expect(screen.getByText(/Cursor at A ready/)).toBeInTheDocument();
1019+
expect(screen.getByText(/Cursor at A, ready/)).toBeInTheDocument();
10201020
});
10211021

10221022
it("empty input is not submitted", () => {
@@ -1092,7 +1092,7 @@ describe("GildedRosePanel — simulation details", () => {
10921092

10931093
it("CSV label is visible", () => {
10941094
goToGR();
1095-
expect(screen.getByText(/Inventory CSV one item per line/)).toBeInTheDocument();
1095+
expect(screen.getByText(/Inventory CSV, one item per line/)).toBeInTheDocument();
10961096
});
10971097

10981098
it("start button is disabled and shows 'Starting...' during loading", async () => {

src/client/components/OverviewPanel.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ const EXERCISES: {
2929
name: "Morse Code",
3030
lang: "Perl",
3131
color: "#4ade80",
32-
why: "A text-processing problem at its core \u2014 splitting on delimiters and mapping through a lookup table. Perl is the language for this domain.",
32+
why: "A text-processing problem at its core: splitting on delimiters and mapping through a lookup table. Perl is the language for this domain.",
3333
tests: 232,
3434
},
3535
{
3636
id: "on-screen-keyboard",
3737
name: "On-Screen Keyboard",
3838
lang: "Python",
3939
color: "#38bdf8",
40-
why: "Scripting device input on a smart TV \u2014 a space where Python dominates. Clean data modeling makes the solution readable and extensible.",
40+
why: "Scripting device input on a smart TV, a space where Python dominates. Clean data modeling makes the solution readable and extensible.",
4141
tests: 94,
4242
},
4343
{
@@ -69,15 +69,15 @@ export function OverviewPanel({ onNavigate }: Props) {
6969
<div style={styles.panel}>
7070
<h2 style={styles.title}>Overview</h2>
7171
<p style={styles.desc}>
72-
Six exercises, six languages &mdash; each language chosen to fit the problem it solves.
72+
Six exercises, six languages, each chosen to fit the problem it solves.
7373
Every exercise prompt suggests approaching it as part of a larger system, so this dashboard
7474
ties them all together.
7575
</p>
7676

7777
<div style={styles.philosophy}>
7878
<div style={styles.philHeading}>Approach</div>
7979
<p style={styles.philText}>
80-
Each exercise uses a language suited to its problem domain &mdash; Rust for
80+
Each exercise uses a language suited to its problem domain: Rust for
8181
financial math, Perl for text processing, Go for interface-driven business rules, and so
8282
on. The table below explains the reasoning behind each choice.
8383
</p>

src/client/components/exercises/CashRegisterPanel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,14 @@ export function CashRegisterPanel() {
8080
optimal change.
8181
</ContextBox.Section>
8282
<ContextBox.Section heading="Solution">
83-
<Lang>Rust</Lang> &mdash; Financial calculations demand zero-cost abstractions and type
83+
<Lang>Rust</Lang>. Financial calculations demand zero-cost abstractions and type
8484
safety. All arithmetic uses integer cents to eliminate floating-point rounding bugs
8585
entirely. A trait-based strategy pattern (<Code>ChangeStrategy</Code>) cleanly separates
8686
the greedy optimal algorithm from the randomized path, making new strategies a one-file
8787
addition.
8888
</ContextBox.Section>
8989
<ContextBox.Section heading="Testing">
90-
<Stat>79 tests</Stat> &mdash; covers exact change, overpayment with every denomination,
90+
<Stat>79 tests</Stat> covering exact change, overpayment with every denomination,
9191
the divisible-by-3 randomization path, penny-level precision, large amounts, zero-change
9292
edge cases, and round-trip verification that computed change always sums back to the
9393
correct amount.

src/client/components/exercises/GildedRosePanel.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,17 +149,17 @@ export function GildedRosePanel() {
149149
<ContextBox>
150150
<ContextBox.Section heading="Problem">
151151
Implement a system where shop inventory items degrade (or improve) in Quality each day
152-
according to category-specific rules &mdash; 5 item types, each with unique aging
152+
according to category-specific rules across 5 item types, each with unique aging
153153
behavior.
154154
</ContextBox.Section>
155155
<ContextBox.Section heading="Solution">
156-
<Lang>Go</Lang> &mdash; Go's interface system is a natural fit. Each item category
156+
<Lang>Go</Lang>. Go's interface system is a natural fit. Each item category
157157
implements a single <Code>Updater</Code> interface, and a registry pattern makes adding
158158
new item rules a one-file addition. Go's simplicity forces clean design without hiding
159159
behind language features.
160160
</ContextBox.Section>
161161
<ContextBox.Section heading="Testing">
162-
<Stat>82 tests</Stat> &mdash; covers all 5 category rules (Normal, Aged, Legendary,
162+
<Stat>82 tests</Stat> covering all 5 category rules (Normal, Aged, Legendary,
163163
BackstagePass, Conjured), boundary conditions at Quality 0 and 50, SellIn expiration
164164
behavior, multi-day progression, and edge cases like negative SellIn values.
165165
</ContextBox.Section>
@@ -192,7 +192,7 @@ export function GildedRosePanel() {
192192
</div>
193193
</div>
194194
<div style={styles.csvLabel}>
195-
Inventory CSV &mdash; one item per line:{" "}
195+
Inventory CSV, one item per line:{" "}
196196
<code style={styles.code}>Name,Category,SellIn,Quality</code>
197197
</div>
198198
<textarea

src/client/components/exercises/MissingNumberPanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export function MissingNumberPanel() {
55
return (
66
<ExercisePanel
77
title="Missing Number"
8-
description="Paste a sequence of numbers with one value missing it finds the gap."
8+
description="Paste a sequence of numbers with one value missing, and it finds the gap."
99
endpoint="/api/exercises/missing-number"
1010
placeholder="comma-separated numbers"
1111
buttonLabel="Find It"
@@ -18,13 +18,13 @@ export function MissingNumberPanel() {
1818
be unsorted and can use any consistent step size (1, 2, 3, etc.).
1919
</ContextBox.Section>
2020
<ContextBox.Section heading="Solution">
21-
<Lang>Zig</Lang> &mdash; A tight algorithmic problem suited to a systems language. Zig's
21+
<Lang>Zig</Lang>. A tight algorithmic problem suited to a systems language. Zig's
2222
explicit allocator model and comptime features show well on small, performance-sensitive
2323
code. The solution detects the step size from the sorted input, then uses arithmetic sum
2424
comparison to pinpoint the missing value in O(n log n) time.
2525
</ContextBox.Section>
2626
<ContextBox.Section heading="Testing">
27-
<Stat>53 tests</Stat> &mdash; covers step sizes of 1 through 5, unsorted input, negative
27+
<Stat>53 tests</Stat> covering step sizes of 1 through 5, unsorted input, negative
2828
numbers, missing first/last elements, two-element sequences, large sequences (1000+
2929
elements), and malformed input rejection. Uses a custom test runner for verbose per-test
3030
output.

src/client/components/exercises/MorseCodePanel.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,18 @@ export function MorseCodePanel() {
3939

4040
<ContextBox>
4141
<ContextBox.Section heading="Problem">
42-
Build a two-way Morse code translator &mdash; text to Morse and back again &mdash;
42+
Build a two-way Morse code translator (text to Morse and back again),
4343
including multi-word messages with proper delimiter hierarchy.
4444
</ContextBox.Section>
4545
<ContextBox.Section heading="Solution">
46-
<Lang>Perl</Lang> &mdash; A text-processing problem at its core &mdash; splitting on
46+
<Lang>Perl</Lang>. A text-processing problem at its core: splitting on
4747
delimiters and mapping through a lookup table. Perl is <em>the</em> language for this
4848
domain. The solution uses a three-tier delimiter hierarchy (dot for signals, pipe-pair for
4949
letters, quad-pipe for words) and builds both encoder and decoder from a single
5050
authoritative lookup table.
5151
</ContextBox.Section>
5252
<ContextBox.Section heading="Testing">
53-
<Stat>200 tests</Stat> &mdash; covers every letter A&ndash;Z, digits 0&ndash;9, all
53+
<Stat>200 tests</Stat> covering every letter A&ndash;Z, digits 0&ndash;9, all
5454
supported punctuation, multi-word encoding/decoding, full round-trip verification (encode
5555
then decode returns original), edge cases like empty input, unknown characters, and
5656
malformed Morse sequences.

src/client/components/exercises/OnScreenKeyboardPanel.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ function KeyboardViz({ output }: { output: string }) {
193193

194194
{/* Current move indicator */}
195195
<div style={styles.moveIndicator}>
196-
{current.move === "start" && "Cursor at A ready"}
196+
{current.move === "start" && "Cursor at A, ready"}
197197
{current.move === "U" && "\u2191 Up"}
198198
{current.move === "D" && "\u2193 Down"}
199199
{current.move === "L" && "\u2190 Left"}
@@ -306,13 +306,13 @@ export function OnScreenKeyboardPanel() {
306306
D, L, R, S) for each input word.
307307
</ContextBox.Section>
308308
<ContextBox.Section heading="Solution">
309-
<Lang>Python</Lang> &mdash; If this were production code, it would be scripting device
310-
input on a smart TV &mdash; a space where Python dominates. The layout is modeled as data
309+
<Lang>Python</Lang>. If this were production code, it would be scripting device
310+
input on a smart TV, a space where Python dominates. The layout is modeled as data
311311
(a list of row strings) with an index dict for O(1) character lookups. Manhattan distance
312312
cursor movement keeps the algorithm clean and extensible to different keyboard layouts.
313313
</ContextBox.Section>
314314
<ContextBox.Section heading="Testing">
315-
<Stat>74 tests</Stat> &mdash; covers single characters, full words, phrases with spaces,
315+
<Stat>74 tests</Stat> covering single characters, full words, phrases with spaces,
316316
wraparound edge cases, every character on the keyboard, multi-word input, and output
317317
format verification. Tests validate both the move sequence and final cursor position.
318318
</ContextBox.Section>

src/client/components/reviews/ReviewSection.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export function ReviewSection({ refreshKey, onMutate }: Props) {
129129
<option value="">Select restaurant...</option>
130130
{restaurants.map((r) => (
131131
<option key={r.id} value={r.id}>
132-
{r.name} {r.city}
132+
{r.name}, {r.city}
133133
</option>
134134
))}
135135
</select>
@@ -140,11 +140,11 @@ export function ReviewSection({ refreshKey, onMutate }: Props) {
140140
aria-label="Select rating"
141141
>
142142
<option value="">Rating...</option>
143-
<option value="1">1 Poor</option>
144-
<option value="2">2 Fair</option>
145-
<option value="3">3 Good</option>
146-
<option value="4">4 Great</option>
147-
<option value="5">5 Excellent</option>
143+
<option value="1">1 - Poor</option>
144+
<option value="2">2 - Fair</option>
145+
<option value="3">3 - Good</option>
146+
<option value="4">4 - Great</option>
147+
<option value="5">5 - Excellent</option>
148148
</select>
149149
<textarea
150150
value={body}
@@ -180,7 +180,7 @@ export function ReviewSection({ refreshKey, onMutate }: Props) {
180180
{reviews.length === 0 && !listApi.loading && (
181181
<tr>
182182
<td colSpan={5} style={styles.emptyRow}>
183-
No reviews yet &mdash; select a user and restaurant above to create one
183+
No reviews yet. Select a user and restaurant above to create one.
184184
</td>
185185
</tr>
186186
)}

0 commit comments

Comments
 (0)