Skip to content

Take the duration chips off the list, and let the list say what it was asked - #95

Open
EnesYilmazcode wants to merge 2 commits into
mainfrom
claude/open-issues-eymdb2-85
Open

Take the duration chips off the list, and let the list say what it was asked#95
EnesYilmazcode wants to merge 2 commits into
mainfrom
claude/open-issues-eymdb2-85

Conversation

@EnesYilmazcode

Copy link
Copy Markdown
Owner

Closes #85

What changed

The chip bar is gone from the page. #chips, its four .chip buttons, its role="radiogroup", its 22 lines of CSS, attachChips (the roving-tabindex and arrow-key handler), the $('chips').hidden = name !== 'list' line in showPane, and the #chips arm of the sheet's drag exclusion.

The list gained the line the chips were carrying. paintList now renders note + asked() + strip + caveat + rows, where asked() is one paragraph:

You asked for 2h00.

.asked is prose at the weight of .prov: dim, .8rem, no border, no target, nothing to press.

paintChips became paintDuration and now only does the half that was never about chips — toggling .primary on the four #ask .opt[data-min] buttons.

Why the line says what it says

Two decisions, both about not overclaiming:

  • "You asked for", not "Free for". strip goes empty as soon as one row meets the ask, and the rows under it can be shorter. A heading reading "Free for 2h00" over those rows is a promise the list does not keep. "You asked for 2h00." is true in all four of paintList's states, including the one whose strip says nothing near you is free for that long.
  • It spends dur(state.needed), the same call the strip and the empty screen already make two lines away, rather than the chips' own 30m / 1h / 2h / rest of day. One figure on one screen gets one vocabulary. It is also the only honest rendering of "rest of day", which is minutes to the last minute the schedule covers and not a fixed length.

The empty-results branch does not get the line: it is not a silent list, it opens with an h2 that states the answer in words, and its last branch already prints dur(state.needed) in a sentence of its own.

Hide vs delete: deleted

Deleted, deliberately. $('chips').hidden = true is one character of diff and leaves a four-button radiogroup, a roving-tabindex handler, an arrow-key handler and 22 lines of CSS in the shell every phone downloads, all wired to a node nothing renders. scripts/check-dead-code.mjs cannot see any of it — the handlers stay reachable from DOMContentLoaded, so they are live code by every measure the checker has, pointed at nothing. It is still green (83 files, no dead code), which is exactly why hiding would have been the worse outcome: the checker would have gone on reporting clean over a dead control, and the next person to read attachChips would have had no way to tell.

#back

Unchanged, and confirmed: showList sets aria-label to Back to the question, index.html carries the same string on #back, and the four .opt buttons it lands on are the same four choices in a bigger target. That is now the only route to the duration, so a test pins the label, the markup and the four choices together.

Numbers

Sizes, measured here with gzip -9:

file raw gzipped
index.html 36,329 → 35,397 11,365 → 11,270
js/app.js 95,137 → 96,034 32,494 → 32,841

Net +252 bytes gzipped on the shell: markup and CSS came off, and the comments explaining the two decisions above went on.

The 61px bar / 57px row / 19%-of-a-321px-sheet figures in the commit message and in docs/DECISIONS.md are arithmetic on the shipped CSS and on PEEK = 0.38, not a rendered measurement — no browser ran in this environment. They are the issue's own figures, and they check out against the rules that were in the file.

Tests

npm test: 669 tests, 665 pass, 0 fail, 4 skipped. Baseline on main was 666 / 662 / 0 / 4, so the three new tests are the whole difference and nothing regressed.

Added to scripts/test/screens.test.mjs:

  • the chips are gone from the page, not merely hidden on it — no #chips, no .chip, no radiogroup in the markup or the stylesheet, and nothing in js/app.js still reaching for them
  • the ranked list says which question it is answering — asked() is in the innerHTML concatenation, it is a <p> and not a control, it spends dur(state.needed), and it does not say "free for"
  • back is the way to the question, and it says so on the list

Mutation-checked: deleting asked() + from the concatenation fails the second test, and the suite passes again when it is restored.

Docs

  • docs/a11y-contract.md: new section "What the list is a list of" (the line, its weight, what it is not allowed to claim, and #back's name); the two stale chip clauses under Live regions and Tap targets rewritten
  • docs/DECISIONS.md: an appended dated entry recording hide-vs-delete, the two copy decisions, the measured byte cost, and what is not measured
  • README.md and docs/BLUEPRINT.md: "duration chip" → the question screen's buttons, where the prose describes the live app. docs/BACKLOG.md and older DECISIONS.md entries are history and were left alone.
  • comments in js/dev.js, js/install.js, scripts/test/install.test.mjs and scripts/test/screens.test.mjs that named the chips. The install-test comment records a real 36-case measurement, so it keeps the measurement and gains a line saying the chips have since gone and why the rule outlived them.

What I did NOT do

  • docs/media/list.webp and list-full.webp were not reshot, and they still show the chip bar. scripts/shoot.mjs needs Chrome or Chromium and there is none on this machine (CHROME unset, nothing in /usr/bin, no Playwright cache). No test reads the chips out of frames.json — the alt-text checks hold README numbers against recorded screen text, and neither frame's text nor either alt string mentions the chips — so readme.test.mjs is green on a stale picture rather than broken. Someone with a browser needs to run node scripts/shoot.mjs before those frames are believed again. Nothing was skipped or weakened to get there.
  • sw.js was not re-stamped. scripts/stamp-sw.mjs writes the current HEAD short SHA and is documented as running in the job that commits. Running it here would have stamped the parent commit and put every parallel branch in conflict on the same line. The shell changed, so whoever deploys should stamp.
  • js/engine.js:80's comment still says RELAX_LADDER "mirrors the duration chips the UI offers" — true of the four durations, stale about the word. Left alone deliberately: the ranking engine is being edited in parallel.
  • paintList's edit is one added asked() + line in the concatenation and a block of new comments above the function, kept clear of the four-state strip logic that The engine says a relaxed answer "has to admit it" and returns the rung that produced it; the app never reads it #90 is rewriting.

🤖 Generated with Claude Code

https://claude.ai/code/session_017xiX95r6nnUUmhD3KwMnWM


Generated by Claude Code

…s asked

The chip bar kept asking "how long?" on every row of every answer, on the one
screen whose whole job is showing answers. Read off the rule that shipped it was
61px (44px min-height, .5rem padding top and bottom, a 1px border-top) against a
57px result row, and PEEK is 0.38, so on a 390x844 phone it was 19% of the 321px
peeked sheet: slightly more than one whole answer, permanently. The room screen
had already stopped showing it.

#chips is deleted rather than hidden. Hiding is one character of diff and leaves
a four-button radiogroup, a roving-tabindex handler, an arrow-key handler and 22
lines of CSS in the shell every phone downloads, all wired to a node nothing
renders, and check-dead-code.mjs cannot see any of it because the handlers are
still reachable from DOMContentLoaded.

The chip bar was the only place the list stated the duration. paintList renders
note + strip + caveat + rows and the strip is empty as soon as one row meets the
ask, so the list gains one quiet non-interactive line above the rows:

  You asked for 2h00.

It says what was ASKED, not what is offered: "Free for 2h00" over rows that can
be shorter is a promise the list does not keep. It spends dur(state.needed), the
same call the strip and the empty screen already make two lines away, so one
figure on one screen has one vocabulary, and it is the only honest rendering of
"rest of day", which is not a fixed length. The empty screen does not get the
line, because it opens with an h2 that states the answer in words already.

paintChips becomes paintDuration and only syncs the four .opt buttons on the
question screen. attachChips and its DOMContentLoaded call are gone.

#back is unchanged and still reads "Back to the question" on the list, which is
now the only route to the duration. Three tests hold both halves together, and
one holds the label.

Measured with gzip -9: index.html 36,329 to 35,397 raw, 11,365 to 11,270
gzipped; js/app.js 95,137 to 96,034 raw, 32,494 to 32,841 gzipped. Net +252
bytes gzipped on the shell, all of it the comments explaining the two decisions.

docs/media/list.webp and list-full.webp still show the chip bar. scripts/shoot.mjs
needs a Chrome that is not on this machine, and no test reads the chips out of
those frames, so the suite is green on a stale picture. They have to be reshot.

Closes #85

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xiX95r6nnUUmhD3KwMnWM
…new page

Two defects found reviewing this branch, both in the half of #85 that is
about not lying.

The line said "You asked for 30 min." to someone who pressed "rest of
day". neededMinutes() returns Math.max(30, latestEnd - now), so inside the
last half hour of the index's day the clamp wins and dur() prints the floor
instead of the ask. Measured on the shipped index, it is wrong every day:
Mon 21:26-21:54, Tue 21:16-21:44, Wed 21:21-21:49, Thu 21:16-21:44, Fri
20:06-20:34, Sat 15:31-15:59 all render "30 min" for a button that does not
say 30 min, indistinguishable from the button that does. At 08:00 the same
call printed the 12h15 the app derived rather than the thing the user
chose. The line names the button for "rest of day" and keeps dur() for the
three fixed lengths, which is true at every minute of the day.

The second is why this could not ship as it stood. sw.js serves navigations
network-first and assets cache-first, so the first load after a deploy runs
the new index.html against the js/app.js still in the shell cache. That
app.js calls attachChips() as the second statement of its DOMContentLoaded
listener, and its first line is $('chips').querySelectorAll('.chip'). With
#chips deleted that is a TypeError, and it lands before #back, popstate,
attachSheet() and boot() are wired: every returning visitor gets a screen
frozen on "finding campus..." over four disabled buttons with no back arrow
and no error. Re-stamping SHELL_CACHE does not save that load, because the
old worker is the one serving it, and js/pwa.js only auto-reloads while the
page is untouched, which a frozen screen will not stay. An empty hidden
#chips costs 30 bytes and makes the old forEach run over nothing. It is
dated in the markup and pinned by a test, and goes after one release.

The guard test asserted `id="chips"` was absent, so it now pins the shim as
empty and temporary instead. Its stylesheet patterns move to \b: the rule
this change actually deleted was `.row, .chip, .opt, ... {`, and a comma is
not whitespace, a brace or a bracket, so the old pattern walked past the
shape it was written to catch. Comments are stripped before those scans, as
sw.test.mjs already does, so prose naming the thing cannot read as the
thing returning.

Shell gzip figure re-measured over the new bytes: 85,456 on GNU gzip 1.12,
85,300 on node's zlib, 0.183% apart against a 1% tolerance. The header
named gzip 1.14; the tool that produced this number is 1.12.

670 tests, 666 pass, 0 fail. check-dead-code --strict: 83 files, no dead code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xiX95r6nnUUmhD3KwMnWM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The duration chips should not follow you onto the list; the back arrow is already the way back to the question

2 participants