Skip to content

feat(web): personal board — private tasks beside the work board - #75

Open
tym83 wants to merge 4 commits into
mainfrom
feat/personal-board
Open

feat(web): personal board — private tasks beside the work board#75
tym83 wants to merge 4 commits into
mainfrom
feat/personal-board

Conversation

@tym83

@tym83 tym83 commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Attach your own private GitHub Project as a personal board next to the work board. Personal cards get the full aeman experience — zones, sprints, carry-over, the weekly plan — while staying invisible to everyone else.

What

  • Me view splits into two panes: Work (unchanged MeBoard) and Personal (an embedded MeBoard: zone bands + stats, no duplicated toolbar/notes chrome). On narrow screens (≤820px) one pane shows at a time with a clamped ‹ › switcher.
  • Team view gains an owner-only virtual Personal chip that repoints the whole team machinery (people×zones grid, sprint state, carry-over, weekly plan) at the personal project's no-team group.
  • Setup is a small dialog: the owner is pinned to the signed-in login, only the project number is asked; attaching the work board itself is refused, and loading the personal project as the work board detaches the pointer with a visible warning.
  • A failing personal board degrades to a dismissible warning and never takes down the work board. "View as" never shows a personal pane. In lock-board mode the feature is disabled entirely (see below).

Why

The Me view is a day plan; real days mix work and personal tasks, but personal tasks must not live on a shared project. A second, user-owned private project gives them a home with zero server changes.

How it holds together

  • Privacy is mechanical, not conventional: every GitHub call already runs under the requesting user's token and the board cache is gated per login, so a private personal project is unreadable and uncacheable for anyone else. The pointer to it is a per-login localStorage preference and never reaches the server. The personal pane posts no presence.
  • Two instances, no federation: the board machinery is extracted verbatim into useBoardData and instantiated per board — each owns its state, scoped watch socket and server address, so mutations, notes and logs route to the right project by construction. Server, REST API and MCP: zero changes (Go diff is empty).
  • Lock-board mode disables the feature: the server pins every request's owner/project to the locked board, so a "personal" request would silently be served the work project. Gated behind one tested helper (personalPaneVisible); a server-side @me-style allowance is a possible follow-up.

Testing

  • 37 vitest cases (12 new: pointer persistence, same-project guard, pane clamps, the visibility gate incl. lock-board), typecheck clean, go test ./... green, full Hugo-less SPA build embedded and exercised.
  • Live end-to-end against a real server and two sandbox projects: same-project guard both directions, card creation verified to land on the personal project only (checked via gh project item-list on both), personal team mode with carry-over and weekly plan, the narrow-mode switcher, broken-pointer degradation, keyboard isolation between panes.
  • Design doc: docs/design/personal-board.md; behaviour rows P1–P10 in docs/design/behavior-matrix.md.

Residual notes

  • Cards created in the personal project outside aeman without a self-assign appear in the personal team view (Unassigned column) but not in the Me pane until assigned — MeView filters by assignee, as on any board.
  • The pointer survives logout within the same browser profile (the cards themselves never leak — they are token-gated).
  • Attaching a public project under your own login is not warned about; the project's privacy remains the user's responsibility. A visibility check via the API is a possible follow-up.
  • The 61px horizontal overflow on very narrow screens reproduces on main's plain Me board with any card — pre-existing, not addressed here.

tym83 and others added 4 commits July 22, 2026 09:13
Attach your own private GitHub Project as a personal board:

- Me view splits into Work and Personal panes (narrow screens switch
  panes with a clamped ‹ › bar); the personal pane is an embedded MeBoard
  (zone bands + stats, no duplicated toolbar/notes chrome).
- Team view gains an owner-only virtual Personal chip that repoints the
  whole team machinery (grid, sprint, carry-over, weekly plan) at the
  personal project's no-team group — personal tasks get real sprints.
- Privacy needs no server work: every GitHub call already runs under the
  requesting user's token and the board cache is gated per login, so a
  private personal project is invisible to everyone else. The pointer to
  it is a per-login localStorage preference and never leaves the client.
- Implementation is two instances of the board machinery (useBoardData,
  extracted verbatim from App): each board owns its state, scoped watch
  socket and server address, so mutations, notes and logs route to the
  right project by construction. Server, API and MCP: zero changes.
- A failing personal board degrades to a dismissible warning and never
  takes down the work board; attaching the work board itself is refused;
  view-as never shows a personal pane.

Design: docs/design/personal-board.md; behaviour rows P1-P8 in
docs/design/behavior-matrix.md. Frontend: typecheck clean, 33 vitest
cases pass (8 new); Go: untouched, full test suite passes.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: tym83 <6355522@gmail.com>
- BLOCKER: lock-board mode disabled the guarantee, not the feature — the
  server pins every request to the locked project, so a 'personal' board
  would silently BE the shared work project and private cards would land
  where the whole team reads them. Everything personal is now gated on
  !lockBoard through one tested helper (personalPaneVisible): no button,
  no load, no pane, no chip. Design doc told the opposite; fixed.
- Same-project guard now covers both directions: the dialog refused
  attaching the work board, but loading the personal project AS the work
  board created two instances over one project (doubled cards,
  cross-routed mutations). doLoad now detaches the pointer with a
  visible warning.
- Column-order localStorage is board-scoped (legacy global key read as
  fallback): a shuffle on the personal team view no longer overwrites
  the work board's hand-picked column order.
- Pane arrows go through the tested prevPane/nextPane transitions; the
  pane/chip gate is a pure tested helper (P2/P2a/P7 rows are now pinned
  by tests, matching the matrix's Test column convention).
- reset() clears the queue badge and its debounce timer (no phantom
  pending counter after detach); a personal card's detail never falls
  back to the work board and is closed on detach; component reloads
  clear their error banner again (pre-refactor contract).
- Docs: lock-board paragraph rewritten to the truth, stale tag/route
  test claims removed, behavior-matrix P-rows get the Test column.

37 vitest cases pass (4 new), typecheck clean, SPA builds, Go untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: tym83 <6355522@gmail.com>
Found by live end-to-end testing (attach tym83/1, then repoint to a
nonexistent #99): the load effect reset state only on detach, so a FAILED
load of a new pointer left the previous board alive — the pane kept
rendering (and would keep mutating) the old project while the toolbar
already named the new one. Reset first on every pointer change, and drop
a stale error from a superseded load via an effect-scoped cancel flag.

Also verified live in the same session: same-project guard (both
directions), card creation routed to the personal project only (checked
via gh item-list on both projects), personal team mode (grid, carry-over,
weekly plan against the personal project), the ≤820px pane switcher, and
the broken-pointer warning leaving the work board untouched. The 61px
horizontal overflow seen on narrow screens reproduces on main's plain Me
board with any card — pre-existing, out of scope here.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: tym83 <6355522@gmail.com>
Two more real bugs from the deep re-verification pass:

- Both MeBoard instances bound global keydown handlers: with the dual Me
  view an ArrowDown moved selection in BOTH panes and Shift+Arrow
  reordered cards on BOTH projects at once. The embedded pane no longer
  binds global keys (mouse/touch only), and the work pane ignores keys
  while hidden by the narrow-mode switcher (offsetParent check) — so a
  hardware keyboard on a phone cannot drive the invisible pane either.
  Verified live: ArrowDown selects in the work pane only.
- useBoardData.load() now carries a generation: reset() and any newer
  load() orphan an in-flight load's eventual result. Previously a SLOW
  successful load resolving after detach resurrected the detached
  board's state and its watch socket (the a9bd071 cancel flag covered
  only the error path); a repoint A->B with A's response arriving last
  could land A's board under B's pointer.

Also verified this round: per-instance DndContext means a drag can never
cross panes/projects; TeamBoard conditional slots genuinely remount (no
state leakage between work/personal instances); StrictMode double-invoke
of the doLoad pointer-updater is idempotent. Behavior matrix: P9, P10.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: tym83 <6355522@gmail.com>
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.

1 participant