Skip to content

fix(web): close the UI follow-ups from #456 - #496

Merged
dubadub merged 10 commits into
mainfrom
fix/followups
Sep 6, 2026
Merged

fix(web): close the UI follow-ups from #456#496
dubadub merged 10 commits into
mainfrom
fix/followups

Conversation

@dubadub

@dubadub dubadub commented Sep 6, 2026

Copy link
Copy Markdown
Member

Fixes the eight follow-ups listed in #456, one commit each.

Verification

  • cargo fmt, cargo clippy --all-targets, cargo test clean.
  • Playwright (Chromium, serial): 146 passed, 5 pre-existing skips, 0 failed.
  • Static build checked: 19 assets, no stylesheet sources.

Filed while reviewing

Closes #486, closes #487, closes #488, closes #489, closes #490, closes #491, closes #492, closes #493.

https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu

The inline search script in base.html interpolated `recipe.path` and
`recipe.name` straight into innerHTML, so a recipe name containing markup
was rendered as markup. Escape both through the same helper search.js
uses in static mode, and cover it with an E2E test that serves a
fabricated result through a route intercept.

Closes #487

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
clearList() added `hidden` to #shopping-list-results and nothing ever
removed it, so the "no items" message rendered into #list-content stayed
invisible until a reload, and live updates from another tab rendered
into a hidden container. Drop the toggle and have displayShoppingList()
un-hide the container whenever it renders.

Closes #489

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
The "Saved" label on /edit/<recipe> was not the result of a save: an
unconditional `updateSaveStatus('saved')` ran at script load as an
"initial status". No PUT fires on open and the file's mtime is
untouched, so the fix is to leave the status empty until a real save.
The new editor spec asserts no PUT, an empty status and an unchanged
mtime after opening the page.

Closes #491

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
Endpoint summaries on /api-docs become h3 so the h4 sub-labels no longer
sit directly under the section h2, and section names in the recipe steps
column become h2 since they follow the page h1 directly.

Closes #492

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
The icon-only buttons carried English aria-label/title literals (theme,
keyboard shortcuts, more options, preferences, dismiss, scale stepper).
Add aria-* keys to all seven locales and route the templates through
tr.t(). The shortcuts modal's Close button lives in keyboard-shortcuts.js,
so base.html now exposes window.__STRINGS__ and the script reads the
label from there with an English fallback.

Closes #488

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
The index was sorted in byte order, so uppercase names came before
lowercase ones and "Recipe 10" landed before "Recipe 9". The client
then re-sorted with Intl.Collator (numeric, case-insensitive), which
left the no-JS static-site first paint in a different order and opened
a reorder flash.

Sort on the server with the same key the client uses: directories
first, then a case-insensitive natural comparison that treats digit
runs numerically, with byte order as a deterministic tiebreak.

Closes #486

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
…site

copy_static_assets copied the whole embedded static/ tree, so the
generated site included static/css/input.css, static/css/components.css
and static/js/src/*, none of which any page links. Skip those build
inputs so only output.css, cooking-mode.css and the runtime scripts
are written.

Closes #493

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
The Cooklang highlight style hard-coded hex colours in editor.js, so
they did not follow the theme and some read poorly on the dark editor
surface. Give each token kind a fixed .cm-cook-* class instead and
colour it from the theme tokens in input.css, next to the other
CodeMirror rules, so every colour flips with light/dark like the rest
of the editor chrome.

Closes #490

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
The server's natural sort folds case and digits but not accents, so the
client no longer re-sorts on load for name/asc: the first paint is the
canonical order and there is no reorder on load. The comment on
natural_key says what it does and does not fold, and a test pins it.

Refs #486

Claude-Session: https://claude.ai/code/session_013urND2B6Y3Z7WQuDpE8ZDu
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown

Review of #496

Nice work — this is a well-organized PR: each of the eight follow-ups from #456 is a focused, isolated commit with its own test, and the descriptions make it easy to verify each fix independently. I read through the full diff (locales, src/build/writer.rs, src/web/builders.rs, templates, JS, and the new/updated Playwright specs). I couldn't execute cargo build/cargo test/Playwright myself in this sandbox, so the notes below are from static reading only — the verification steps in the PR description (fmt/clippy/test clean, 146 Playwright passing) cover what I can't run here.

Correctness

Minor / nitpicks (non-blocking)

  1. Duplicated HTML-escaping logic: static/js/keyboard-shortcuts.js defines its own inline escape (.replace(/&/g,...)...) instead of reusing the escapeHtml() already defined in base.html (templates/base.html:402). Since keyboard-shortcuts.js is loaded via a <script> tag after that inline script runs, the global escapeHtml function is already available on window at call time — the duplication could be dropped. Very low risk either way since it's static translation strings being escaped, not attacker-controlled input.
  2. aria-close vs aria-dismiss: both translate to essentially "Close"/"Dismiss" in every locale and are used for conceptually similar close/dismiss icon buttons. Not a bug, just a slight naming overlap that could be consolidated to one key if you want to trim the locale files later.
  3. The new EXCLUDED_STATIC_PREFIXES list is a hardcoded prefix list rather than something derived from what templates actually reference — fine for the current small set of assets, but worth remembering to update if new unbundled source files are added under static/ (the added test is_shipped_asset_excludes_sources_only will catch drift only if someone remembers to extend it too).

Security

Test coverage

Good coverage overall — each behavioral fix (#486, #487, #489, #491, #493) has a corresponding unit or e2e test. #488 (aria-label i18n) and #490/#492 (CSS/heading-level changes) are lower-risk template/CSS tweaks and reasonably left without dedicated new tests, though a quick a11y-focused Playwright assertion (e.g., checking aria-label is non-empty/localized on the icon buttons) could be a nice future addition given the existing accessibility.spec.ts suite.

Overall: looks ready to merge pending your own fmt/clippy/test run confirmation. Nice attention to detail on tying each fix back to a regression test.

@dubadub
dubadub merged commit 9b19857 into main Sep 6, 2026
6 checks passed
@dubadub
dubadub deleted the fix/followups branch September 6, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment