Skip to content

fix(board): sync the board after buddy and authored card writes (#233) - #271

Draft
daniilperkin wants to merge 2 commits into
devfrom
hotfix/233-sync-buddy-and-authored-cards-with-board
Draft

daniilperkin wants to merge 2 commits into
devfrom
hotfix/233-sync-buddy-and-authored-cards-with-board

Conversation

@daniilperkin

Copy link
Copy Markdown
Collaborator

Related issue

#233 — Synchronize Buddy-created checklists with the board immediately

Short summary

"Keep as checklist" wrote the card server-side, but the board kept serving what it had already read — a stale React Query cache, not a stale server. The write path (POST /me/board/cards) had no relationship to the board query, so an open board behind the floating buddy dock never refetched at all, and a visit within the 30 s staleTime was served the pre-card board.

This PR makes every board write that happens outside the board itself mark the board cache stale, following the invalidation pattern PathStepCard already established. The board then shows the change on its next read — immediately when it is open — with no manual refresh.

The surfaces that now sync

Surface Write Invalidates
SaveReplyToBoard ("Keep as checklist") checklist from an AI reply — the reported defect board.byProject(selectedProjectId)
SaveToBoard ("Keep on my board") notes/links kept from chat, buddy replies, task cards board.byProject(selectedProjectId)
SelectionActions ("Add to board") card authored from a text selection board.byProject(selectedProjectId)
useBuddyConversation.confirmAction confirmed buddy board actions: place_checklist, amend_checklist, tick_checklist_items, reword_checklist_item, place_note — and claim_goal, which pins the CURRENT_TASK card board.all()
useBuddyConversation.sendMessage the confirm-free place_card tool, which applies mid-answer board.all() when the turn ran it (failure path included)

Decisions worth knowing

  • The buddy hook invalidates board.all() rather than the selected project's key: the backend re-resolves the project server-side (the caller's single onboarding project) and the client never learns which board it wrote.
  • Writes that fail invalidate nothing — a broken save must not cost the board its cache.
  • claim_goal is in the board-action set because confirming it writes twice: the claim, and the CURRENT_TASK card ("It's on your board too").
  • place_card is deliberately not confirmed, so the tool_use event during the turn is the only signal a card landed; the sync is applied when the turn ends, the failing path included — a turn that placed a card and then broke still wrote one.
  • Not touched: useGeneratedPathCards' addCard loop — its only caller (BoardPage.handleGenerate) already calls useBoard().refresh() after applying the plan; useBoard's own mutations self-manage.

Checks

  • I verified the code makes sense intuitively
  • The PR changes affect only this issue, no unrelated/unwanted code changes to other modules/code segments
  • CI runs (npm run format:check, npm run lint, npm run build)
  • New business logic is unit tested (tests/unit/features/buddy/SaveReplyToBoard.test.tsx, tests/unit/features/board/SaveToBoard.test.tsx, tests/unit/features/buddy/useBuddy.test.tsx, tests/unit/features/board/SelectionActions.test.tsx)
  • The new functionality is tested manually in browser with active Keycloak session

How to verify manually

  1. Open /board first (so its cache is warm), then open the buddy dock without leaving the page. Ask something the buddy answers with a list, press Keep as checklist — the new checklist must appear on the board behind the dock without pressing Refresh.
  2. Load /board, close it, go to /buddy and keep a reply as a checklist within 30 s. Navigate back to /board — the card must be there without Refresh.
  3. In /buddy, trigger a board action (checklist/note proposal) and confirm it — the board must reflect it on next view; a failed confirm must leave the board untouched.
  4. Ask the buddy to put a task on the board ("put … on my board") — the card must land without a manual refresh.
  5. Highlight text anywhere, press Add to board, then open /board — the card must be there.

Commits

  • 28300d49 fix(board): sync the cached board after buddy and authored card writes
  • 02bf8038 test(board): pin board invalidation across buddy and save surfaces

Verified locally on this branch: npm run format:check green, npm run build green, npm run lint green, npm run unit green (330 files / 3211 tests), npm run a11y green (55 files / 69 tests).

Issue #233: keeping an AI reply as a checklist wrote the card server-side,
but the board kept serving what it had already read — a stale cache, not a
stale server. The write path (POST /me/board/cards) had no relationship to
the board query, so an open board behind the floating dock never refetched
at all, and a visit within the 30s `staleTime` was served the pre-card
board.

This closes every write into the board that lives outside the board itself,
following the pattern PathStepCard already established:

- SaveReplyToBoard ("Keep as checklist") — the reported defect.
- SaveToBoard ("Keep on my board") — the shared button behind chat
  replies, buddy replies and task cards.
- SelectionActions ("Add to board") — cards authored from a selection.
- useBuddyConversation — the buddy's own board writes:
  - confirmed board actions (place/amend/tick/reword checklist, place_note)
    invalidate after a write that actually succeeded. `claim_goal` is in
    the set too: confirming it also pins the CURRENT_TASK card onto the
    board ("It's on your board too").
  - the `place_card` tool applies mid-stream with no confirmation step, so
    the `tool_use` event is the only signal a card landed; the turn marks
    the board stale when it ends — the failing path included, since a turn
    that placed a card and then broke still wrote one.

The three component paths invalidate by project (`board.byProject`); the
buddy hook invalidates `board.all()`, because the backend re-resolves the
project server-side for buddy writes and the client never learns which
board it wrote. Writes that fail invalidate nothing — a broken save must
not cost the board its cache.

The remaining addCard caller, useGeneratedPathCards, needs nothing here:
its only caller (BoardPage's generate flow) already calls the board's own
`refresh` after applying the plan.

Refs #233
Covers the sync behaviour the fix introduces, and the boundaries it must
not cross:

- SaveReplyToBoard (new): the #233 regression itself — saving the checklist
  marks `board.byProject(selectedProjectId)` stale; a reply without a list
  renders nothing; a failed save toasts and leaves the cache alone.
- SaveToBoard (new): the shared "keep this" button invalidates once the
  card is really on the board, still reports to `onSaved` for its callers,
  and does neither when the write failed.
- SelectionActions (existing, one test added): the toolbar's save marks the
  board stale.
- useBuddy (existing, new `board synchronisation` describe): a confirmed
  board action marks the board stale, `claim_goal` included; a failed
  confirm and a non-board action (request_attestation) do not; a turn that
  ran `place_card` marks it, and a turn that ran other tools does not.

Two harness notes for whoever extends these next: the seeded board query is
observer-less (pure `setQueryData`), so it must not get `gcTime: 0` — it
would be garbage-collected before the invalidation can be observed and
`isInvalidated` would read as `undefined`. And the save-button test clears
storage via an optional call (`window.localStorage?.clear()`) because jsdom
ships with or without a Storage backing depending on the Node version in
use.

Refs #233
@daniilperkin daniilperkin linked an issue Sep 27, 2026 that may be closed by this pull request
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.

Synchronize Buddy-created checklists with the board immediately

1 participant