Claude: changes for issue #159 #162

Merged
Grandiras merged 2 commits from claude/issue-159 into main 2026-08-26 20:38:37 +00:00
Collaborator

Closes #159.

Closes #159.
Add a central search over every board
All checks were successful
CI / build (pull_request) Successful in 32s
9874ca7b5d
One box in the middle of the header — the magnifier below 960px, and the
middle of the phone's bottom bar — that finds a card on any board this
member can see, by anything written on or around it.

A word is looked for in five places at once: the title, the tags, the
names of the people on the card, the board and column it lives in, and
the description. It counts wherever it lands, so a card tagged "urgent"
whose description says "time to deploy" is found by "urgent deploy" and
neither word is in its title. Half-typed words, typos one or two edits
off (transpositions cost one), and both languages' plurals are covered by
the matching rules rather than by a stemmer.

Four operators narrow it: @somebody (assignee or author, by handle or
display name), tag:, date: and board:. Repeating one widens, using two
narrows. date: takes today/tomorrow/week/month/overdue/none, a month name
in either language, and 2027 / 2027-02 / 2027-02-14. Both languages'
spellings are accepted whichever language is on screen, because the
operators are syntax; the flyout teaches the one that matches what the
member is reading, before they have typed anything.

It is deliberately not an embedding model, and docs/search.md says why:
every searchable column is AES-encrypted with non-deterministic
ciphertext, so there is nothing for a vector index to sit beside — a
plaintext embedding beside a ciphertext column would undo the encryption
for the one table anybody would attack, and an encrypted one can only be
read by decrypting all of it, which is the in-memory pass this already
is plus 90 MB of model in the image.

SearchService reads the viewer's cards once per search session, scores
them against every keystroke after that, and drops the decrypted snapshot
when the box closes — a plaintext copy of a member's whole board set is
not a thing to hold for the life of a circuit. Two collection includes
and no comments, on CardService.GetColumnsAsync's rule.

- SearchQuery / CardSearch are pure and carry the tests Playwright cannot
  reach; tests/e2e/search.spec.js pins the box, the tip and the phone's
  full-screen mode.
- docs/search.md is new and in CLAUDE.md's table; orientation, ui,
  frontend and live-updates follow it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Author
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/389); 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-162/run-389.md) — updating every ~5 minutes while the task runs.
claude-reviewer approved these changes 2026-08-26 20:28:54 +00:00
Dismissed
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Large, well-scoped feature (in-memory cross-board search) that closely follows the repo's documented conventions. I traced the core logic in detail:

  • Access control: SearchService.SnapshotAsync builds its card set strictly from BoardService.GetVisibleAsync(viewer) — the same canonical access check used elsewhere — so a search cannot surface cards from boards the viewer can't see. No encrypted column is filtered/ordered/indexed in SQL, per the data.md rule; only BoardColumn.BoardId (plaintext) is used in the Where.
  • Parsing (SearchQuery): operator detection only triggers at the head of a whitespace-delimited token (verified against the board:9 inside a URL and mail@example.test test cases), quoting/unclosed-quote handling, bilingual date words/month names, and the ambiguous-month-abbreviation guard (Ju → null) all check out against their tests.
  • Ranking (CardSearch): weight/quality composition, phrase matching, the bounded Damerau–Levenshtein implementation (verified the three-row rotation indexing and the transposition-cost math by hand against the caledanr/calendar case), and the budget+1 sentinel handling are all correct and match the extensive CardSearchTests.
  • Component (SearchBox.razor): debounce/cancellation pattern is sound (deliberately not disposing the cancelled CancellationTokenSource to avoid the ObjectDisposedException-on-Token-read gotcha, which is called out in a comment), IAsyncDisposable + base.Dispose() matches LocalizedComponentBase's documented pattern, all rendered fields go through Blazor's automatic encoding (no injected raw HTML), and JS interop follows the established single-watcher-slot convention from prefs-interop.js/calendar-interop.js.
  • Conventions: new locale keys present in both en/de tables including the .one plural variant for search.results; changelog entries correctly appended to Changelog.Unreleased in both languages; new docs page (search.md) is cross-linked from CLAUDE.md, orientation.md, live-updates.md, ui.md, and frontend.md consistent with the repo's doc-routing convention.

No correctness or security issues found.


⏱ 3m 10s · 14 turns · tokens: 85.6k in / 14.5k out (+1337.4k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Large, well-scoped feature (in-memory cross-board search) that closely follows the repo's documented conventions. I traced the core logic in detail: - **Access control**: `SearchService.SnapshotAsync` builds its card set strictly from `BoardService.GetVisibleAsync(viewer)` — the same canonical access check used elsewhere — so a search cannot surface cards from boards the viewer can't see. No encrypted column is filtered/ordered/indexed in SQL, per the data.md rule; only `BoardColumn.BoardId` (plaintext) is used in the `Where`. - **Parsing (`SearchQuery`)**: operator detection only triggers at the head of a whitespace-delimited token (verified against the `board:9` inside a URL and `mail@example.test` test cases), quoting/unclosed-quote handling, bilingual date words/month names, and the ambiguous-month-abbreviation guard (`Ju` → null) all check out against their tests. - **Ranking (`CardSearch`)**: weight/quality composition, phrase matching, the bounded Damerau–Levenshtein implementation (verified the three-row rotation indexing and the transposition-cost math by hand against the `caledanr`/`calendar` case), and the `budget+1` sentinel handling are all correct and match the extensive `CardSearchTests`. - **Component (`SearchBox.razor`)**: debounce/cancellation pattern is sound (deliberately not disposing the cancelled `CancellationTokenSource` to avoid the ObjectDisposedException-on-Token-read gotcha, which is called out in a comment), `IAsyncDisposable` + `base.Dispose()` matches `LocalizedComponentBase`'s documented pattern, all rendered fields go through Blazor's automatic encoding (no injected raw HTML), and JS interop follows the established single-watcher-slot convention from `prefs-interop.js`/`calendar-interop.js`. - **Conventions**: new locale keys present in both `en`/`de` tables including the `.one` plural variant for `search.results`; changelog entries correctly appended to `Changelog.Unreleased` in both languages; new docs page (`search.md`) is cross-linked from `CLAUDE.md`, `orientation.md`, `live-updates.md`, `ui.md`, and `frontend.md` consistent with the repo's doc-routing convention. No correctness or security issues found. --- ⏱ 3m 10s · 14 turns · tokens: 85.6k in / 14.5k out (+1337.4k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-162/run-389.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:9874ca7b5d7da0e84434764de40e6ea70a4991d0 -->
Owner

@claude-bot Fix the merge conflict

@claude-bot Fix the merge conflict
Author
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

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

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/390); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-162/run-390.md) — updating every ~5 minutes while the task runs.
Merge remote-tracking branch 'origin/main' into claude/issue-159
All checks were successful
CI / build (pull_request) Successful in 33s
0138cc1f8c
# Conflicts:
#	src/Ponente.Kanban/Services/Changelog.cs
Author
Collaborator

Merged main into claude/issue-159 and pushed — the PR is conflict-free again.

The conflict was a single one, in Services/Changelog.cs: both sides appended a bullet to Changelog.Unreleased at the same position, in both locales. #161 added the local-profile line; this branch added the search line. Both are real changes a member notices, so I kept both — profile first (it landed on main first), search after — in en and de.

Everything else auto-merged cleanly. I checked the one place the two features could actually collide: search reads Member.DisplayName for its @name operator, and #161 deliberately writes the local name into that same column rather than a second one, so the people index needs no change. MemberService.SetProfileAsync raises NotifyAllBoardsChanged, which SearchService already subscribes to, so a renamed member invalidates the per-circuit search snapshot rather than going stale. This branch adds no migration, so #161's AddLocalProfiles remains the only new one.

Verified on the merge commit:

  • dotnet build Ponente.sln — clean, 0 warnings.
  • dotnet test Ponente.sln — 444/444 passing (including DocumentationTests).
  • Ran the app against a local Postgres; migrations applied, UsesLocalProfile present.
  • Playwright against that live server: search.spec.js (2), profile.spec.js + board.spec.js (27) — all passing. This is the real check, since it exercises both features on the merged code. (The full compose stack isn't available here — no podman/docker — so I ran the specs against the dev server with baseURL pointed at :5000; that override was reverted and is not in the commit.)

The resolved unreleased block, rendered by the running app:

What's new dialog showing three unreleased entries: due-date/assignee changes, Ponente-only profile, and cross-board search


⏱ 4m 29s · 37 turns · tokens: 38.3k in / 8.2k out (+1548.7k cache-read)
📜 Full session transcript

Merged `main` into `claude/issue-159` and pushed — the PR is conflict-free again. **The conflict** was a single one, in `Services/Changelog.cs`: both sides appended a bullet to `Changelog.Unreleased` at the same position, in both locales. #161 added the local-profile line; this branch added the search line. Both are real changes a member notices, so I kept both — profile first (it landed on `main` first), search after — in `en` and `de`. Everything else auto-merged cleanly. I checked the one place the two features could actually collide: search reads `Member.DisplayName` for its `@name` operator, and #161 deliberately writes the local name into that same column rather than a second one, so the people index needs no change. `MemberService.SetProfileAsync` raises `NotifyAllBoardsChanged`, which `SearchService` already subscribes to, so a renamed member invalidates the per-circuit search snapshot rather than going stale. This branch adds no migration, so #161's `AddLocalProfiles` remains the only new one. **Verified on the merge commit:** - `dotnet build Ponente.sln` — clean, 0 warnings. - `dotnet test Ponente.sln` — 444/444 passing (including `DocumentationTests`). - Ran the app against a local Postgres; migrations applied, `UsesLocalProfile` present. - Playwright against that live server: `search.spec.js` (2), `profile.spec.js` + `board.spec.js` (27) — all passing. This is the real check, since it exercises both features on the merged code. (The full compose stack isn't available here — no podman/docker — so I ran the specs against the dev server with `baseURL` pointed at `:5000`; that override was reverted and is not in the commit.) The resolved unreleased block, rendered by the running app: ![What's new dialog showing three unreleased entries: due-date/assignee changes, Ponente-only profile, and cross-board search](https://git.grandiras.net/attachments/3db26fad-a6e9-483e-9372-779ceb1bb92d) --- ⏱ 4m 29s · 37 turns · tokens: 38.3k in / 8.2k out (+1548.7k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-162/run-390.md)
Author
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/391); 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-162/run-391.md) — updating every ~5 minutes while the task runs.
claude-reviewer left a comment

✅ Verdict: approve — no blocking issues found.

Re-review: the only new commit on this branch since my previous approval is a no-op merge of main into claude/issue-159 (9874ca7 is the actual feature commit, 0138cc1 is just the merge). Diffing the branch against the current main (ce5e61f) shows byte-identical content to what I already reviewed and approved — no substantive changes. Standing approval holds: access control goes through BoardService.GetVisibleAsync, no encrypted column is filtered/ordered in SQL, parsing/ranking logic matches its extensive test coverage, locale keys and changelog entries are present in both languages, and the new docs/search.md is cross-linked from all the relevant pages per convention.


⏱ 0m 26s · 10 turns · tokens: 64.8k in / 1.6k out (+864.6k cache-read)
📜 Full session transcript

✅ **Verdict: approve** — no blocking issues found. Re-review: the only new commit on this branch since my previous approval is a no-op merge of `main` into `claude/issue-159` (9874ca7 is the actual feature commit, 0138cc1 is just the merge). Diffing the branch against the current `main` (ce5e61f) shows byte-identical content to what I already reviewed and approved — no substantive changes. Standing approval holds: access control goes through `BoardService.GetVisibleAsync`, no encrypted column is filtered/ordered in SQL, parsing/ranking logic matches its extensive test coverage, locale keys and changelog entries are present in both languages, and the new `docs/search.md` is cross-linked from all the relevant pages per convention. --- ⏱ 0m 26s · 10 turns · tokens: 64.8k in / 1.6k out (+864.6k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/pr-162/run-391.md) <!-- claude-verdict:approve --> <!-- claude-reviewed-head:0138cc1f8ca93b60ab9d1b9d072d11d03c4ca5e0 -->
Grandiras deleted branch claude/issue-159 2026-08-26 20:38:37 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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!162
No description provided.