Make search the v2 palette: centred on desktop, full screen on a phone #254

Merged
Grandiras merged 3 commits from ui-v2-search into main 2026-10-04 17:01:02 +00:00
Owner

Search in the UI language v2, to the Figma draft (Desktop · Search 42:2792 / 73:5839, Phone · Search 57:1615 / 73:12731, and the search parts in Calendar & search parts 34:180).

What changed

  • Desktop and tablet: a centred palette. The sidebar's field (a button dressed as a field, 34px, recessed) and the 600–959px magnifier open one palette, centred on the page beside the sidebar 96px down, over a scrim that dims the sidebar too. It holds a 44px field ringed in the accent, a row of Try chips, the results grouped by board (the board's mark, name and count; each row the mark, the title, Board › Column and the due chip; the active row tinted in its own board's colour) and a footer: ↑ ↓ to move · Enter opens · Esc closes. The list scrolls between the query and the footer, and the active row scrolls into sight.
  • Phone: the whole screen instead of the bottom sheet from #243: the field at the top beside Cancel, the chips under it in one row, the results in white groups under the board's name (rows say only the column), sized as the draft (17px titles, 13px meta). It stops on the measured keyboard (--keyboard-inset), so the last row can be scrolled into sight.
  • Chips are the operators SearchQuery really reads: date:today, date:week (datum:heute, datum:woche in German), tag:, @, board:. Each is lit while the parsed query uses it, so datum:heute lights date:today; a date chip is a whole term and a second press takes it off; the others are typed on. Their mousedown is cancelled, so the field keeps focus (and a phone its keyboard). The old four rows of tips are gone; their explanations are the chips' tooltips.
  • Behaviour kept: ranking and operators, rows as real links to /board/{key}?card={id}, the comment-only marker (now in the place line), Up/Down/Enter/Escape in C#, the arrows kept off the caret and the field letting go on Enter (#249), / and Ctrl/⌘-K, @bind, closing keeps the query (and opening now searches it again, since closing dropped the snapshot).
  • Escape closes it from anywhere in the palette (the palette is focusable and takes it; the field's own keys stop at the field) and returns focus to the trigger. The input is the combobox's aria-activedescendant owner; groups are role="group" in the listbox.
  • Over a docked card. The header is the palette's stacking context, so it rises to 1100 while the palette is open (:has): the docked inspector (a .modal-backdrop at 1000) used to paint over its right half, and Ctrl/⌘-K from inside a card dialog opened it invisibly behind.
  • SearchDocument loses Preview and TagNames, which only the old rows showed (one CardPreview.Build less per card per snapshot).
  • Docs: search.md (The box, rewritten with the reasons), ui.md (the phone sheets), frontend.md (v2 status line, search-interop.js, CardPreview). design/trace/capture.mjs's desktop search scenario opens the palette from the new trigger.

Judgement calls

  • Due chips are date-only for now. The draft shows Tomorrow in orange; that state (card-due-soon, card.due.today/tomorrow, --warning) arrives with #252. Adding the keys here would collide with #252 or leave duplicates, so search rows wear card-due plus card-due-overdue, which #252 restyles automatically. Once #252 is in, the soon state is a three-line follow-up in SearchBox (I can do it).
  • Accents follow the page, as the draft's desktop frame does (its field ring is the Summer fair board's blue): field ring, lit chip and Cancel take the page's accent; each row takes its own board's.
  • The phone field is 16px, not 14px: under 16px iOS zooms the page into a focused field.
  • The clear (×) button stays in the field though the draft shows none: it is existing behaviour and the slow-circuit spec uses it.
  • No halo around the palette field: the draft's render has the 2px ring only.
  • The tablet (600–959px) gets the desktop palette, centred in the window; there is no frame for it.

Verified

  • tools/verify.sh: build and 584 unit tests green.
  • Full Playwright suite on an isolated, port-shifted copy of the test stack: 162 passed, 0 failed, no flakes. search.spec.js rewritten for the new shapes and extended: the trigger opening a focused palette, chips (typing on, lighting, a second press, either language), grouping and counts, Escape from the padding, focus back on the trigger, Ctrl-K, the palette over a docked card, centring and the scrim at 960px, the 620px window, and the phone's full screen (edge to edge over the tab bar, field at the top, list below it, Cancel).
  • Screenshots at 1440×900 and 390×844, light and dark, compared side by side with the Figma frames (also 800px and over a docked card).

🤖 Generated with Claude Code

Search in the UI language v2, to the Figma draft (*Desktop · Search* 42:2792 / 73:5839, *Phone · Search* 57:1615 / 73:12731, and the search parts in *Calendar & search parts* 34:180). ## What changed - **Desktop and tablet: a centred palette.** The sidebar's field (a button dressed as a field, 34px, recessed) and the 600–959px magnifier open one palette, centred on the page beside the sidebar 96px down, over a scrim that dims the sidebar too. It holds a 44px field ringed in the accent, a row of **Try** chips, the results **grouped by board** (the board's mark, name and count; each row the mark, the title, *Board › Column* and the due chip; the active row tinted in its own board's colour) and a footer: *↑ ↓ to move · Enter opens · Esc closes*. The list scrolls between the query and the footer, and the active row scrolls into sight. - **Phone: the whole screen** instead of the bottom sheet from #243: the field at the top beside **Cancel**, the chips under it in one row, the results in white groups under the board's name (rows say only the column), sized as the draft (17px titles, 13px meta). It stops on the measured keyboard (`--keyboard-inset`), so the last row can be scrolled into sight. - **Chips are the operators SearchQuery really reads**: `date:today`, `date:week` (`datum:heute`, `datum:woche` in German), `tag:`, `@`, `board:`. Each is lit while the parsed query uses it, so `datum:heute` lights `date:today`; a date chip is a whole term and a second press takes it off; the others are typed on. Their mousedown is cancelled, so the field keeps focus (and a phone its keyboard). The old four rows of tips are gone; their explanations are the chips' tooltips. - **Behaviour kept**: ranking and operators, rows as real links to `/board/{key}?card={id}`, the comment-only marker (now in the place line), Up/Down/Enter/Escape in C#, the arrows kept off the caret and the field letting go on Enter (#249), `/` and Ctrl/⌘-K, `@bind`, closing keeps the query (and opening now searches it again, since closing dropped the snapshot). - **Escape** closes it from anywhere in the palette (the palette is focusable and takes it; the field's own keys stop at the field) and **returns focus to the trigger**. The input is the combobox's `aria-activedescendant` owner; groups are `role="group"` in the listbox. - **Over a docked card.** The header is the palette's stacking context, so it rises to 1100 while the palette is open (`:has`): the docked inspector (a `.modal-backdrop` at 1000) used to paint over its right half, and Ctrl/⌘-K from inside a card dialog opened it invisibly behind. - `SearchDocument` loses `Preview` and `TagNames`, which only the old rows showed (one `CardPreview.Build` less per card per snapshot). - Docs: `search.md` (*The box*, rewritten with the reasons), `ui.md` (the phone sheets), `frontend.md` (v2 status line, `search-interop.js`, `CardPreview`). `design/trace/capture.mjs`'s desktop search scenario opens the palette from the new trigger. ## Judgement calls - **Due chips are date-only for now.** The draft shows *Tomorrow* in orange; that state (`card-due-soon`, `card.due.today/tomorrow`, `--warning`) arrives with #252. Adding the keys here would collide with #252 or leave duplicates, so search rows wear `card-due` plus `card-due-overdue`, which #252 restyles automatically. Once #252 is in, the soon state is a three-line follow-up in `SearchBox` (I can do it). - **Accents follow the page**, as the draft's desktop frame does (its field ring is the Summer fair board's blue): field ring, lit chip and Cancel take the page's accent; each row takes its own board's. - **The phone field is 16px, not 14px**: under 16px iOS zooms the page into a focused field. - **The clear (×) button stays** in the field though the draft shows none: it is existing behaviour and the slow-circuit spec uses it. - **No halo around the palette field**: the draft's render has the 2px ring only. - **The tablet (600–959px) gets the desktop palette**, centred in the window; there is no frame for it. ## Verified - `tools/verify.sh`: build and 584 unit tests green. - Full Playwright suite on an isolated, port-shifted copy of the test stack: **162 passed, 0 failed, no flakes**. `search.spec.js` rewritten for the new shapes and extended: the trigger opening a focused palette, chips (typing on, lighting, a second press, either language), grouping and counts, Escape from the padding, focus back on the trigger, Ctrl-K, the palette over a docked card, centring and the scrim at 960px, the 620px window, and the phone's full screen (edge to edge over the tab bar, field at the top, list below it, Cancel). - Screenshots at 1440×900 and 390×844, light and dark, compared side by side with the Figma frames (also 800px and over a docked card). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Make search the v2 draft's palette: centred on desktop, full screen on a phone
All checks were successful
CI / build (pull_request) Successful in 43s
47d9ffe7f1
The sidebar's field and the magnifier now open one palette, centred on the
page beside the sidebar over a dimmed page: a large field, "Try" chips for the
operators SearchQuery really reads (lit while the query uses them; a date
comes off again on a second press), results grouped by board with its mark and
count, each row the board's mark, the title, Board > Column and the due chip,
and a footer of keys. Below 600px it is the whole screen, the field at the top
beside Cancel and the results under it, instead of the bottom sheet.

The header rises while the palette is open, so it sits over a docked card and
Ctrl-K reaches it from one. Escape from anywhere in it closes it and returns
focus to the trigger; the field still lets go on Enter. The active row scrolls
into sight and is the combobox's active descendant.

SearchDocument loses Preview and TagNames, which only the old rows showed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-23-58-620Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/698); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-698.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-04 15:24:38 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

The change looks good to merge. I read the diff only and did not run the build or the Playwright suite, so I'm relying on the PR's reported results (584 unit tests, 162 e2e specs).

  • Single query copy: the sidebar field is now a button, so the query lives only in the palette. @bind is kept.
  • Opening: Open() sets _open before its first await and OnShortcut returns the task, so Ctrl/⌘-K and the triggers behave the same. Opening re-runs a kept query, which fixes the dropped-snapshot case.
  • Escape: the field's stopPropagation avoids a double dismiss. Dismiss() refocuses both triggers in turn, and focusing the hidden one is a harmless no-op.
  • Ranking and order: GroupBy preserves first occurrence, so grouped boards still follow their best hit and the first row is still the best. The _hits.Index() index matches the on-screen order.
  • Date chips: Use removes a lit date in whichever language it was typed (DateOf(t) != date). Appending the other chips is unchanged.
  • JS: dropping the stored field for a matches('.nav-search-input') check is sound, because the input exists only while the palette is open. reveal() runs after the render that moves the active row.

Minor things, none blocking:

  • aria-modal="true" has no focus trap, so Tab can leave the palette for the page behind the scrim.
  • The "no match" and "searching" <p> sits inside the role="listbox" element, which is not a valid child for that role.
  • @onmousedown:preventDefault on the whole results panel also blocks text selection there. Scrollbar dragging is probably fine, but I did not check it in all browsers.

🧠 claude-sonnet-5-5 · ⏱ 0m 19s · 3 turns · tokens: 46.3k in / 2.5k out (+68.9k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. The change looks good to merge. I read the diff only and did not run the build or the Playwright suite, so I'm relying on the PR's reported results (584 unit tests, 162 e2e specs). - **Single query copy:** the sidebar field is now a button, so the query lives only in the palette. `@bind` is kept. - **Opening:** `Open()` sets `_open` before its first await and `OnShortcut` returns the task, so Ctrl/⌘-K and the triggers behave the same. Opening re-runs a kept query, which fixes the dropped-snapshot case. - **Escape:** the field's `stopPropagation` avoids a double dismiss. `Dismiss()` refocuses both triggers in turn, and focusing the hidden one is a harmless no-op. - **Ranking and order:** `GroupBy` preserves first occurrence, so grouped boards still follow their best hit and the first row is still the best. The `_hits.Index()` index matches the on-screen order. - **Date chips:** `Use` removes a lit date in whichever language it was typed (`DateOf(t) != date`). Appending the other chips is unchanged. - **JS:** dropping the stored `field` for a `matches('.nav-search-input')` check is sound, because the input exists only while the palette is open. `reveal()` runs after the render that moves the active row. Minor things, none blocking: - `aria-modal="true"` has no focus trap, so Tab can leave the palette for the page behind the scrim. - The "no match" and "searching" `<p>` sits inside the `role="listbox"` element, which is not a valid child for that role. - `@onmousedown:preventDefault` on the whole results panel also blocks text selection there. Scrollbar dragging is probably fine, but I did not check it in all browsers. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 19s · 3 turns · tokens: 46.3k in / 2.5k out (+68.9k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-698.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:47d9ffe7f11d3c1304df285bc313592185c4ceed -->
Collaborator

🤖 Resolving the merge conflicts — the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the fix runs.

🤖 Resolving the merge conflicts — the reply will link the full session transcript. <!-- opencode-run:conflict --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-30-18-043Z.md) — updating every ~5 minutes while the fix runs.
Collaborator

Merge committed cleanly (ee9c604). Everything verified — here's the summary for the PR comment.


Merge conflict resolved

What conflicted: docs/frontend.md — one bullet, "Two things lead the app" (the adopted v2 spec list in the design-canvas section). Both commits rewrote the same sentence from the same base.

  • This branch added the search palette to the v2 list: …the Done deck ([ui.md](ui.md)), drag physics (*Drag and drop* below) and the search palette, full screen on a phone ([search.md](search.md)).
  • main (#251) added the card dialog's inspector groups and the phone's card sheet at two heights to the list, and rewrote the exceptions tail accordingly.

How I resolved it: Both additions survive in one bullet. For the tail, I kept main's newer version of the exceptions ("except large titles that collapse as a page scrolls and the large card sheet's receded page; and Escape saves where the draft reverts, and the footnote leaves out '⌘Z undoes'"). The branch's old exception — "the card sheet's two detents" — was true when the branch left main, but #251 has since built the card sheet at two heights, so keeping it would state the opposite of what the app now does; it's the same line #251 would have edited had this branch landed first.

The branch's other two edits merged cleanly and are kept: CardPreview no longer listed as "a search row" source (search rows lost preview in this PR), and the search-interop.js bullet gained "scrolling the active row into sight".

Verified:

  • No <<<<<<</=======/>>>>>>> markers anywhere in the tree.
  • No unmarked breakage: main replaced wwwroot/js/inspector.js with card-dialog.js — nothing on this branch (code or docs) still references the deleted file; SearchBox.razor doesn't call into main's rewritten CardModal internals, and the palette's row-scrolling claim doesn't clash with card-dialog.js's watcher slot.
  • dotnet build on the solution passes; the doc-checking DocumentationTests (8 tests, which validate every doc page's links and Covers: paths in CI) pass.

→ Merge resolved and pushed; a re-review follows.


⏱ 10m 42s · 19 steps · $0.0223 · tokens: 91.3k in / 3.1k out (+169.0k cache-read) · Go: 5h 0% · wk 12% · mo 89%

📜 Full session transcript

Merge committed cleanly (`ee9c604`). Everything verified — here's the summary for the PR comment. --- ## Merge conflict resolved **What conflicted:** `docs/frontend.md` — one bullet, "Two things lead the app" (the adopted v2 spec list in the design-canvas section). Both commits rewrote the same sentence from the same base. - **This branch** added the search palette to the v2 list: `…the Done deck ([ui.md](ui.md)), drag physics (*Drag and drop* below) and the search palette, full screen on a phone ([search.md](search.md))`. - **main (#251)** added the card dialog's inspector groups and the phone's card sheet at two heights to the list, and rewrote the exceptions tail accordingly. **How I resolved it:** Both additions survive in one bullet. For the tail, I kept main's newer version of the exceptions ("except large titles that collapse as a page scrolls and the large card sheet's receded page; and Escape saves where the draft reverts, and the footnote leaves out '⌘Z undoes'"). The branch's old exception — "the card sheet's two detents" — was true when the branch left main, but #251 has since *built* the card sheet at two heights, so keeping it would state the opposite of what the app now does; it's the same line #251 would have edited had this branch landed first. The branch's other two edits merged cleanly and are kept: `CardPreview` no longer listed as "a search row" source (search rows lost preview in this PR), and the `search-interop.js` bullet gained "scrolling the active row into sight". **Verified:** - No `<<<<<<<`/`=======`/`>>>>>>>` markers anywhere in the tree. - No unmarked breakage: main replaced `wwwroot/js/inspector.js` with `card-dialog.js` — nothing on this branch (code or docs) still references the deleted file; `SearchBox.razor` doesn't call into main's rewritten `CardModal` internals, and the palette's row-scrolling claim doesn't clash with `card-dialog.js`'s watcher slot. - `dotnet build` on the solution passes; the doc-checking `DocumentationTests` (8 tests, which validate every doc page's links and `Covers:` paths in CI) pass. → Merge resolved and pushed; a re-review follows. --- ⏱ 10m 42s · 19 steps · $0.0223 · tokens: 91.3k in / 3.1k out (+169.0k cache-read) · Go: 5h 0% · wk 12% · mo 89% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-30-18-043Z.md) <!-- opencode-session:ses_ef8766c2dffeCBIe7iNo3rP3Q9 -->
Merge remote-tracking branch 'origin/main' into ui-v2-search
All checks were successful
CI / build (pull_request) Successful in 43s
ee9c6042af
# Conflicts:
#	docs/frontend.md
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/701); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-701.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-04 15:42:23 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

This is a re-review. I read the diff only and did not run the build or the e2e suite, so I'm relying on the PR's reported results (584 unit tests, 162 e2e specs).

The new commits don't change the picture from my last pass. The three minor notes from then are unchanged:

  • aria-modal has no focus trap.
  • The "no match" and "searching" <p> sits inside the role="listbox" element.
  • @onmousedown:preventDefault on the results panel blocks text selection there.

None of them blocks merging.

I found no new correctness problems.

  • Open() re-runs a kept query, which fixes the dropped-snapshot case.
  • Grouped results keep each board's best hit first.
  • The date chips toggle off in either language.
  • Escape returns focus to whichever trigger is on screen.

🧠 claude-sonnet-5-5 · ⏱ 0m 3s · 2 turns · tokens: 45.5k in / 368 out (+12.2k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. This is a re-review. I read the diff only and did not run the build or the e2e suite, so I'm relying on the PR's reported results (584 unit tests, 162 e2e specs). The new commits don't change the picture from my last pass. The three minor notes from then are unchanged: - `aria-modal` has no focus trap. - The "no match" and "searching" `<p>` sits inside the `role="listbox"` element. - `@onmousedown:preventDefault` on the results panel blocks text selection there. None of them blocks merging. I found no new correctness problems. - `Open()` re-runs a kept query, which fixes the dropped-snapshot case. - Grouped results keep each board's best hit first. - The date chips toggle off in either language. - Escape returns focus to whichever trigger is on screen. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 3s · 2 turns · tokens: 45.5k in / 368 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-701.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:ee9c6042af0f9feb48912ec0438e9dc7bd0516a2 -->
opencode-reviewer left a comment

💬 Verdict: comment — the reviewer did not submit a structured review; the raw output follows.

(empty)


⏱ 30m 2s · 17 steps · $0.0614 · tokens: 201.0k in / 857 out (+429.2k cache-read) · Go: 5h 1% · wk 12% · mo 89%
📜 Full session transcript

💬 **Verdict: comment** — the reviewer did not submit a structured review; the raw output follows. (empty) --- ⏱ 30m 2s · 17 steps · $0.0614 · tokens: 201.0k in / 857 out (+429.2k cache-read) · Go: 5h 1% · wk 12% · mo 89% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-23-58-620Z.md)
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-54-02-604Z.md) — updating every ~5 minutes while the review runs.
opencode-reviewer approved these changes 2026-10-04 15:59:21 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. Re-review: the new commits check out, and the three minor notes from my earlier passes remain — unchanged, none blocking:

  • aria-modal="true" has no focus trap, so Tab can walk out of the palette onto the dimmed page.
  • The "no match" / "searching" <p> sits inside the role="listbox" element, which is not a valid child for that role.
  • @onmousedown:preventDefault on the whole results panel also blocks text selection there.

Spot checks against the checked-out source rather than the diff alone:

  • Chips vs. parsing. SearchQuery's named-date table maps heute→today, woche→week (and morgen/gestern/monat), and ParseDate treats them all through the English names — so DateOf() is spelling-independent, the lit check only matches dated terms, and Use removes a lit date however it was typed. DateFilter is a value-equal record, so Dates.Contains works. date:week spans today..+7 and date:today today..today — two distinct filters, so the two date chips never light together.
  • --board-color / --sidebar-w plumbing. BoardPalette.ColorOf(int) exists (BoardPalette.cs:27) and feeds the palette's inline --board-color that the marks, the active row and the lit chip mix against. --sidebar-w is 0px in :root and 244px only from 960px — the breakpoint where the phone's inset: 0 0 var(--keyboard-inset) 0 overrides the desktop inset, so the centring math applies only where it is meant to.
  • Order. GroupBy + SelectMany flatten while preserving first-seen order, so boards sort by their best hit and the first row is still the best; _hits.Index()'s index is the row's on-screen position, matching nav-search-hit-@index against aria-activedescendant.

A big shape change over SearchBox's state, with the e2e suite extended to pin the new geometry. Ready.


⏱ 5m 19s · 9 steps · $0.0234 · tokens: 102.9k in / 1.5k out (+171.6k cache-read) · Go: 5h 2% · wk 13% · mo 89%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Good to merge. Re-review: the new commits check out, and the three minor notes from my earlier passes remain — unchanged, none blocking: - `aria-modal="true"` has no focus trap, so Tab can walk out of the palette onto the dimmed page. - The "no match" / "searching" `<p>` sits inside the `role="listbox"` element, which is not a valid child for that role. - `@onmousedown:preventDefault` on the whole results panel also blocks text selection there. Spot checks against the checked-out source rather than the diff alone: - **Chips vs. parsing.** `SearchQuery`'s named-date table maps *heute*→today, *woche*→week (and morgen/gestern/monat), and `ParseDate` treats them all through the English names — so `DateOf()` is spelling-independent, the lit check only matches dated terms, and `Use` removes a lit date however it was typed. `DateFilter` is a value-equal record, so `Dates.Contains` works. `date:week` spans today..+7 and `date:today` today..today — two distinct filters, so the two date chips never light together. - **`--board-color` / `--sidebar-w` plumbing.** `BoardPalette.ColorOf(int)` exists (BoardPalette.cs:27) and feeds the palette's inline `--board-color` that the marks, the active row and the lit chip mix against. `--sidebar-w` is 0px in `:root` and 244px only from 960px — the breakpoint where the phone's `inset: 0 0 var(--keyboard-inset) 0` overrides the desktop inset, so the centring math applies only where it is meant to. - **Order.** `GroupBy` + `SelectMany` flatten while preserving first-seen order, so boards sort by their best hit and the first row is still the best; `_hits.Index()`'s index is the row's on-screen position, matching `nav-search-hit-@index` against `aria-activedescendant`. A big shape change over SearchBox's state, with the e2e suite extended to pin the new geometry. Ready. --- ⏱ 5m 19s · 9 steps · $0.0234 · tokens: 102.9k in / 1.5k out (+171.6k cache-read) · Go: 5h 2% · wk 13% · mo 89% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-54-02-604Z.md) <!-- opencode-reviewed-head:ee9c6042af0f9feb48912ec0438e9dc7bd0516a2 --> <!-- opencode-verdict:approve -->
Collaborator

🤖 Resolving the merge conflicts — the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the fix runs.

🤖 Resolving the merge conflicts — the reply will link the full session transcript. <!-- opencode-run:conflict --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-59-22-119Z.md) — updating every ~5 minutes while the fix runs.
Merge remote-tracking branch 'origin/main' into ui-v2-search
All checks were successful
CI / build (pull_request) Successful in 42s
38e6cc9033
# Conflicts:
#	docs/frontend.md
#	docs/ui.md
Collaborator

No container runtime is available in this environment, so the e2e stack isn't reachable — but the repo's own bar (tools/verify.sh: build + unit tests, what CI runs) is fully met. The merge is committed; final summary:

What conflicted

docs/ui.md and docs/frontend.md — both branches rewrote the same v2-adoption passages: the branch (this PR) declared the search palette (centred on desktop, full screen on a phone) part of the adopted v2 set, while main (#252 board screen, #253 Home) declared the board screen and Home part of it and listed search as still to come. Two smaller clashes rode along: search-interop.js gained "scrolling the active row into sight" (branch) while modal-escape.js gained "an open .menu before either" (main) in adjacent bullets; and in ui.md, main superseded the phone board's bar (Share/cog → a ⋯ menu, trayless board, drag hint) in the same hunk as the branch's sheets rewording.

How I resolved it

  • frontend.md, "Two things lead the app": main's full enumeration kept, the branch's "the search palette, full screen on a phone" clause added after drag physics, and "search" removed from main's Still to come list (this PR builds it). Main's longer departures list kept.
  • frontend.md, the JS modules: both additive edits kept — search-interop.js gains row-scrolling (matches its merged code), modal-escape.js gains the .menu handling.
  • frontend.md, Covers line: the branch's CardPreview wording kept (verified: palette rows are mark + title + place + due chip; no CardPreview in SearchBox.razor).
  • ui.md: main's v2 fragments win where they superseded the base text (⋯ menu, trayless board, drag hint, mobile.spec.js pins, Covers: line); the branch's sheets wording survives — on a phone the search covers everything like the card sheet, so the stale "the search's dim is lifted over the bar instead (z-index: 150)" sentence is dropped, consistent with search.md (merged clean, full-screen palette) and the merged app.css (inset: 0, scrim hidden below 600px, header context rises to 1100).

Cross-checked the auto-merged files both sides touched: app.css hunks live in disjoint ranges (braces balanced, every nav-search-* class referenced), LocaleService.cs has both locales' search and board keys, NavHeader.razor still renders <SearchBox />. Diffed the staged docs against each parent: vs origin/main only the branch's search deltas remain, vs HEAD only main's v2 deltas — neither side dropped anything.

Verified

  • dotnet build Ponente.sln: 0 warnings, 0 errors.
  • dotnet test: 600/600 passed, including the branch's CardSearchTests and the DocumentationTests that validate the merged docs' Covers: paths and cross-references.
  • No conflict markers anywhere in the tree; merge committed as 38e6cc9 with both parents intact.
  • E2E: not run — no podman/docker in this container; the repo's stated CI bar (build + unit tests) is what I verified against.

→ Merge resolved and pushed; a re-review follows.


⏱ 18m 45s · 41 steps · $0.1407 · tokens: 325.0k in / 8.4k out (+2352.6k cache-read) · Go: 5h 4% · wk 13% · mo 89%

📜 Full session transcript

No container runtime is available in this environment, so the e2e stack isn't reachable — but the repo's own bar (`tools/verify.sh`: build + unit tests, what CI runs) is fully met. The merge is committed; final summary: ## What conflicted `docs/ui.md` and `docs/frontend.md` — both branches rewrote the same v2-adoption passages: the branch (this PR) declared **the search palette** (centred on desktop, full screen on a phone) part of the adopted v2 set, while main (#252 board screen, #253 Home) declared **the board screen and Home** part of it and listed search as *still to come*. Two smaller clashes rode along: `search-interop.js` gained "scrolling the active row into sight" (branch) while `modal-escape.js` gained "an open `.menu` before either" (main) in adjacent bullets; and in `ui.md`, main superseded the phone board's bar (Share/cog → a ⋯ menu, trayless board, drag hint) in the same hunk as the branch's sheets rewording. ## How I resolved it - **frontend.md, "Two things lead the app"**: main's full enumeration kept, the branch's *"the search palette, full screen on a phone"* clause added after *drag physics*, and "search" removed from main's *Still to come* list (this PR builds it). Main's longer departures list kept. - **frontend.md, the JS modules**: both additive edits kept — `search-interop.js` gains row-scrolling (matches its merged code), `modal-escape.js` gains the `.menu` handling. - **frontend.md, Covers line**: the branch's `CardPreview` wording kept (verified: palette rows are mark + title + place + due chip; no `CardPreview` in `SearchBox.razor`). - **ui.md**: main's v2 fragments win where they superseded the base text (⋯ menu, trayless board, drag hint, `mobile.spec.js` pins, `Covers:` line); the branch's sheets wording survives — on a phone **the search covers everything** like the card sheet, so the stale "the search's dim is lifted over the bar instead (`z-index: 150`)" sentence is dropped, consistent with `search.md` (merged clean, full-screen palette) and the merged `app.css` (`inset: 0`, scrim hidden below 600px, header context rises to 1100). Cross-checked the auto-merged files both sides touched: `app.css` hunks live in disjoint ranges (braces balanced, every `nav-search-*` class referenced), `LocaleService.cs` has both locales' search and board keys, `NavHeader.razor` still renders `<SearchBox />`. Diffed the staged docs against each parent: vs `origin/main` only the branch's search deltas remain, vs `HEAD` only main's v2 deltas — neither side dropped anything. ## Verified - `dotnet build Ponente.sln`: 0 warnings, 0 errors. - `dotnet test`: **600/600 passed**, including the branch's `CardSearchTests` and the `DocumentationTests` that validate the merged docs' `Covers:` paths and cross-references. - No conflict markers anywhere in the tree; merge committed as `38e6cc9` with both parents intact. - E2E: not run — no podman/docker in this container; the repo's stated CI bar (build + unit tests) is what I verified against. → Merge resolved and pushed; a re-review follows. --- ⏱ 18m 45s · 41 steps · $0.1407 · tokens: 325.0k in / 8.4k out (+2352.6k cache-read) · Go: 5h 4% · wk 13% · mo 89% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T15-59-22-119Z.md) <!-- opencode-session:ses_ef85bcf30ffe5bsvW7pMN8NNBB -->
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/705); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-705.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-10-04 16:19:31 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

I read the diff only and did not run the build or the e2e suite. I'm relying on the PR's reported results (584 unit tests, 162 e2e specs).

I found no new correctness problems. My earlier approvals stand.

The three minor notes from the earlier passes are still open. None blocks merging:

  • aria-modal="true" has no focus trap, so Tab can leave the palette for the page behind the scrim.
  • The "no match" and "searching" <p> sits inside the role="listbox" element, which is not a valid child for that role.
  • @onmousedown:preventDefault on the results panel blocks text selection there.

🧠 claude-sonnet-5-5 · ⏱ 0m 7s · 3 turns · tokens: 47.0k in / 366 out (+71.2k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. I read the diff only and did not run the build or the e2e suite. I'm relying on the PR's reported results (584 unit tests, 162 e2e specs). I found no new correctness problems. My earlier approvals stand. The three minor notes from the earlier passes are still open. None blocks merging: - `aria-modal="true"` has no focus trap, so Tab can leave the palette for the page behind the scrim. - The "no match" and "searching" `<p>` sits inside the `role="listbox"` element, which is not a valid child for that role. - `@onmousedown:preventDefault` on the results panel blocks text selection there. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 7s · 3 turns · tokens: 47.0k in / 366 out (+71.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-705.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:38e6cc9033250fe12477981510d304bfd188934d -->
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T16-21-32-389Z.md) — updating every ~5 minutes while the review runs.
opencode-reviewer approved these changes 2026-10-04 16:23:03 +00:00
Dismissed
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. Re-review: the checked-out source matches what I verified in my earlier passes; nothing landed since that changes the picture.

Still holding from before — spot checks against source, not just the diff:

  • Chips vs. parsing: SearchQuery's named-date table maps heute→today, woche→week etc., and DateFilter is a value-equal record, so the lit check and Use's removal are spelling-independent; date:today and date:week are distinct filters, so the two date chips never light together.
  • Plumbing: BoardPalette.ColorOf(int) exists and feeds the inline --board-color; --sidebar-w is 0px at :root and 244px from 960px, matching the centring breakpoint; .board-header at z-index 100 gains 1100 under :has, above the docked card's .modal-backdrop (1000), so the palette paints over it.
  • Order: GroupBy+SelectMany flattens preserving first-seen order, so boards sort by their best hit and the first row is still the best; _hits.Index()'s index matches nav-search-hit-@index against aria-activedescendant.
  • Escape/focus: the field's @onkeydown:stopPropagation avoids a double dismiss; the dual FocusAsync on the triggers is safe because focusing the hidden (display:none) one is a browser no-op.

Three minor notes carried from my earlier passes, unchanged and non-blocking:

  • aria-modal="true" has no focus trap, so Tab can walk out of the palette onto the dimmed page.
  • The "no match" / "searching" <p> sits inside the role="listbox" element, which is not a valid child for that role.
  • @onmousedown:preventDefault on the whole results panel also blocks text selection there.

Relying on the PR's reported results (build + 584 unit tests green, 162 e2e passed) as in previous passes.


⏱ 1m 32s · 10 steps · $0.0176 · tokens: 46.3k in / 1.0k out (+298.5k cache-read) · Go: 5h 4% · wk 13% · mo 89%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Good to merge. Re-review: the checked-out source matches what I verified in my earlier passes; nothing landed since that changes the picture. Still holding from before — spot checks against source, not just the diff: - **Chips vs. parsing**: `SearchQuery`'s named-date table maps *heute*→today, *woche*→week etc., and `DateFilter` is a value-equal record, so the lit check and `Use`'s removal are spelling-independent; `date:today` and `date:week` are distinct filters, so the two date chips never light together. - **Plumbing**: `BoardPalette.ColorOf(int)` exists and feeds the inline `--board-color`; `--sidebar-w` is 0px at `:root` and 244px from 960px, matching the centring breakpoint; `.board-header` at z-index 100 gains 1100 under `:has`, above the docked card's `.modal-backdrop` (1000), so the palette paints over it. - **Order**: `GroupBy`+`SelectMany` flattens preserving first-seen order, so boards sort by their best hit and the first row is still the best; `_hits.Index()`'s index matches `nav-search-hit-@index` against `aria-activedescendant`. - **Escape/focus**: the field's `@onkeydown:stopPropagation` avoids a double dismiss; the dual `FocusAsync` on the triggers is safe because focusing the hidden (display:none) one is a browser no-op. Three minor notes carried from my earlier passes, unchanged and non-blocking: - `aria-modal="true"` has no focus trap, so Tab can walk out of the palette onto the dimmed page. - The "no match" / "searching" `<p>` sits inside the `role="listbox"` element, which is not a valid child for that role. - `@onmousedown:preventDefault` on the whole results panel also blocks text selection there. Relying on the PR's reported results (build + 584 unit tests green, 162 e2e passed) as in previous passes. --- ⏱ 1m 32s · 10 steps · $0.0176 · tokens: 46.3k in / 1.0k out (+298.5k cache-read) · Go: 5h 4% · wk 13% · mo 89% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T16-21-32-389Z.md) <!-- opencode-reviewed-head:38e6cc9033250fe12477981510d304bfd188934d --> <!-- opencode-verdict:approve -->
Grandiras force-pushed ui-v2-search from 38e6cc9033
All checks were successful
CI / build (pull_request) Successful in 42s
to 0c876b9376
All checks were successful
CI / build (pull_request) Successful in 39s
2026-10-04 16:38:45 +00:00
Compare
Collaborator

🤖 Reviewing this PR — the review will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the review runs.

🤖 Reviewing this PR — the review will link the full session transcript. <!-- opencode-run:review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T16-39-46-738Z.md) — updating every ~5 minutes while the review runs.
Collaborator

🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 Reviewing this PR — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/708); the reply will link the full session transcript. <!-- claude-run:pr_review --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-708.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

I read the diff only and did not run the build or the e2e suite. I'm relying on the PR's reported results (584 unit tests, 162 e2e specs).

I found no new correctness problems, so my earlier approvals stand.

Three minor notes from earlier passes are still open. None blocks merging:

  • aria-modal="true" has no focus trap, so Tab can leave the palette for the page behind the scrim.
  • The "no match" and "searching" <p> sits inside the role="listbox" element, which is not a valid child for that role.
  • @onmousedown:preventDefault on the results panel blocks text selection there.

🧠 claude-sonnet-5-5 · ⏱ 0m 4s · 2 turns · tokens: 47.8k in / 305 out (+12.2k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. I read the diff only and did not run the build or the e2e suite. I'm relying on the PR's reported results (584 unit tests, 162 e2e specs). I found no new correctness problems, so my earlier approvals stand. Three minor notes from earlier passes are still open. None blocks merging: - `aria-modal="true"` has no focus trap, so Tab can leave the palette for the page behind the scrim. - The "no match" and "searching" `<p>` sits inside the `role="listbox"` element, which is not a valid child for that role. - `@onmousedown:preventDefault` on the results panel blocks text selection there. --- 🧠 claude-sonnet-5-5 · ⏱ 0m 4s · 2 turns · tokens: 47.8k in / 305 out (+12.2k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-254/run-708.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:0c876b937605cc53eefe49e7cbcc0d47f05bf86d -->
opencode-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Good to merge. Re-review against the checked-out source: it matches what my earlier passes verified, and nothing since has changed the picture. Relying, as before, on the PR's reported results (build + 584 unit tests green, 162 e2e passed).

Fresh spot checks, not just the diff:

  • Chips vs. parsing: SearchQuery's DateWords maps heute→today and woche→week beside the English spellings, and DateFilter is a value-equal record, so DateOf()'s lit check and Use's removal work however the term was spelled; date:today (today..today) and date:week (today..+7) are distinct filters, so the two date chips never light together.
  • Due chips: SearchBox emits card-due-overdue/soon/later — the same ladder CardView.razor uses, with all three classes present in app.css. Note the PR description's judgement-call section still says search rows wear only card-due plus card-due-overdue pending #252; the code has since taken the full state that CardView uses. If #252 restyles .card-due-soon, the follow-up it promises may already be done — worth confirming the description is just stale.
  • Geometry: --sidebar-w is 0px at :root and 244px only from 960px, so the palette's centring math and the desktop trigger breakpoint agree; the phone's inset: 0 0 var(--keyboard-inset, 0px) 0 overrides the desktop inset exactly where the search goes full screen.
  • Escape from the palette's padding: the palette is tabindex="-1", so a click on its non-focusable content (the keys footer, exercised in the e2e spec) focuses the palette itself and its @onkeydown sees Escape; the field's @onkeydown:stopPropagation prevents a double dismiss.

Three minor notes carried from my earlier passes, unchanged and non-blocking:

  • aria-modal="true" has no focus trap, so Tab can walk out of the palette onto the dimmed page.
  • The "no match" / "searching" <p> sits inside the role="listbox" element, which is not a valid child for that role.
  • @onmousedown:preventDefault on the whole results panel also blocks text selection there.

⏱ 1m 4s · 27 steps · $0.0520 · tokens: 88.7k in / 2.2k out (+1155.5k cache-read) · Go: 5h 6% · wk 14% · mo 90%
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Good to merge. Re-review against the checked-out source: it matches what my earlier passes verified, and nothing since has changed the picture. Relying, as before, on the PR's reported results (build + 584 unit tests green, 162 e2e passed). Fresh spot checks, not just the diff: - **Chips vs. parsing**: `SearchQuery`'s `DateWords` maps *heute*→today and *woche*→week beside the English spellings, and `DateFilter` is a value-equal record, so `DateOf()`'s lit check and `Use`'s removal work however the term was spelled; `date:today` (today..today) and `date:week` (today..+7) are distinct filters, so the two date chips never light together. - **Due chips**: SearchBox emits `card-due-overdue`/`soon`/`later` — the same ladder `CardView.razor` uses, with all three classes present in `app.css`. Note the PR description's judgement-call section still says search rows wear only `card-due` plus `card-due-overdue` pending #252; the code has since taken the full state that `CardView` uses. If #252 restyles `.card-due-soon`, the follow-up it promises may already be done — worth confirming the description is just stale. - **Geometry**: `--sidebar-w` is 0px at `:root` and 244px only from 960px, so the palette's centring math and the desktop trigger breakpoint agree; the phone's `inset: 0 0 var(--keyboard-inset, 0px) 0` overrides the desktop inset exactly where the search goes full screen. - **Escape from the palette's padding**: the palette is `tabindex="-1"`, so a click on its non-focusable content (the keys footer, exercised in the e2e spec) focuses the palette itself and its `@onkeydown` sees Escape; the field's `@onkeydown:stopPropagation` prevents a double dismiss. Three minor notes carried from my earlier passes, unchanged and non-blocking: - `aria-modal="true"` has no focus trap, so Tab can walk out of the palette onto the dimmed page. - The "no match" / "searching" `<p>` sits inside the `role="listbox"` element, which is not a valid child for that role. - `@onmousedown:preventDefault` on the whole results panel also blocks text selection there. --- ⏱ 1m 4s · 27 steps · $0.0520 · tokens: 88.7k in / 2.2k out (+1155.5k cache-read) · Go: 5h 6% · wk 14% · mo 90% 📜 [Full session transcript](https://git.grandiras.net/Grandiras/opencode-bot/src/branch/opencode-logs/transcripts/Grandiras/Ponente/pr-254/run-2026-10-04T16-39-46-738Z.md) <!-- opencode-reviewed-head:0c876b937605cc53eefe49e7cbcc0d47f05bf86d --> <!-- opencode-verdict:approve -->
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente!254
No description provided.