Kanban / daily-board view for projax — inventor: research prior art + what to REUSE first #12
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What m wants (2026-07-21)
m wants a Kanban / daily-board capability in projax so he has a proper visual overview of his projects and a daily working board ("damit wir da auch richtig den Überblick haben"). He explicitly asked for this to be an inventor task that researches what already exists and what can be reused, not a build-from-scratch.
Hard framing — m's new standing maxim: REUSE FIRST
m set a maxim the same breath: "unsere neue Maxime … dass wir mehr versuchen, Elemente wiederzuverwenden." So this issue is a research + design task, not an implementation:
[project]nodes,child_of/projax-*scoped edges, the existing views/CLI), then the wider stack m already runs (the otto PWATodayband/quicklog surface from otto#405, any existing board/kanban components in m's repos), then the ecosystem (lightweight embeddable kanban libs) — and what is genuinely reusable vs. net-new.Context
Today m asked otto to structure his work-projects and said the management should live in projax ("Projects") as the system of record. otto is seeding the work-project structure there now; this Kanban/daily-board is the visual layer on top. otto also built a one-off visual map artifact today as a stopgap — the durable home is projax.
Deliverable & gate
Inventor design/scope doc: reuse assessment (the load-bearing part), recommended shape, and a mapped list of follow-up implementation steps. DESIGN READY FOR REVIEW → m reviews before any coder shift (inventor→coder gate). Do NOT implement yet.
Role: inventor. Filed by otto/head from m's live request.
DESIGN READY FOR REVIEW — Kanban / daily-board
Design doc:
docs/plans/kanban-daily-board.mdCommit:
4aa93456c0Branch:
mai/atlas/issue-12-kanban-dailyHeadline: the Kanban already exists
It shipped 2026-05-26 as Phase 5i slice C and is live in prod right now at
/views/tree?view_type=kanban. Columns, cards, group-by chip strip (status / area / tag / management), CSS, tests — all of it.web/kanban.go—BuildKanbanBoard, pure functionweb/templates/tree_kanban.tmpl— board markup + HTMX chip stripweb/static/style.css:232-261—.kanban-board/.kanban-column/.kanban-cardSo the reuse-first answer to "build a Kanban" is: don't. Extend what ships.
The real blocker is not the board — it's the data
Queried live today:
status=activeprojax.views)The Phase 7 task layer is built and completely unused. 72 of 76 projects have no task source of any kind. A daily board is a view over tasks, and projax currently has almost no tasks — build it today and it renders an empty page.
The empty
projax.viewstable is a second signal: the whole saved-views system shipped across two phases (5i + 5j) and has never been used once. Worth asking whether that surface landed wrong rather than stacking onto it.Board vs. daily-board (your Q3)
Two boards over two entities, sharing one engine:
statusThe insight that keeps this cheap: a daily board is a kanban whose
group_byis the due-date bucket — andclassifyDueinweb/dashboard.goalready returns exactly those buckets, tested. So the daily board is one new keyer plus a task-sourced feed, not a new feature.Reuse verdict
Almost everything needed already exists inside projax: the board engine, the CSS, the due-bucketing, the CalDAV task read+write path, the uniform
store.Taskshape over both backends, the saved-views chassis, and evenmbrian.edges.sort_orderfor drag persistence (no migration needed).Outside projax: the otto PWA
Todayband (otto#405) turned out to be not reusable as code — bespoke JSX-to-string + imperative DOM inmAi/pwa, reads CalDAV directly, and it's a vertical list not a board. It contributes exactly one thing: its per-bucket cap + priority tie-break policy, which projax'sclassifyDuelacks.Correction to my own first instinct on drag-and-drop: I was going to recommend SortableJS via CDN. The survey killed it — there is no DnD library anywhere in your repos; mBrian's
BoardView.svelteand paliad'sreorderViaDndboth hand-roll HTML5 drag events. Reuse-first read strictly points at the pattern you already use twice, not a new dependency. So: no library.Proposed slices (not started — inventor→coder gate)
boardsaved view so the existing kanban is findable in the sidebar. Near-zero code.KanbanCardshape so items and tasks feed one engine (avoids a second kanban).group_by=due; extractclassifyDueinto a shared home so dashboard and daily board can't drift.todaysaved view: the daily board proper.TaskWriter.SetTaskStatus/SetTaskDue).Open questions for you
workby default?Nothing implemented, per the gate.
Slices A + B shipped (branch
mai/ritchie/12-slices-a-b-make-the)m greenlit A+B from the design doc. Both pushed, awaiting head merge + deploy-verify.
Slice A — the kanban is findable now
95ce30a— aboardsaved view is seeded on first run (store.SeedDefaultViews, called fromcmd/projax/main.goafter migrations).store.CreateView, not a hand-INSERT — slug format check, reserved-slug list, JSON probe and server-assignedsort_orderall apply.view_type=kanbanlives insidefilter_json(the Phase 5j Q2 encoding — there is noview_typecolumn);group_by=statusis its own column. Iconbox, from the existing 7-key registry; no new icon added.projax.viewsis empty. Once m owns a single view — including a renamed or deleted board — the seed never speaks again. It is a starting point, not a default that fights the user.boardis not in the reserved list (tree/dashboard/calendar/timeline/graph), so no collision.Production currently has 0 rows in
projax.views, so the next deploy creates it.Slice B — one board engine, card-shaped
cc7dbf7—BuildKanbanBoardtook[]*store.Item, so a daily board would have needed a second engine. Now:KanbanCard(id / title / href / meta chips / pinned / sort key) is the only shape the engine and template understand.BuildBoard[T]+BoardSource[T](keyer, card adapter, column order, label) is the engine;BuildKanbanBoardis a thin wrapper supplyingItemCard.store.Taskis deliberately not wired in — that is the daily board, gated on the per-project task-source decision.Zero user-visible change, verified rather than asserted. I captured the rendered
/views/tree?view_type=kanbanbody before and after the refactor and diffed them: identical once inter-tag whitespace is normalised (38631 bytes both sides). The only diff is whitespace between meta chips, which sits inside.kanban-card-meta { display: flex }, where whitespace-only text nodes generate no boxes. All Phase 5i-C kanban tests pass untouched — no assertion was changed.Verification
go build ./...andgo vet ./...clean. New tests: seed-set validation, board spec (view_type=kanban+group_by=status), seed insert path, concurrent-boot collision tolerance, seed icon resolves in the web registry, a view built from the real seed spec renders the kanban, Item→card field/chip-order mapping, the pathless-item edge case, engine source-agnosticism, and unknown column keys sorting after preferred ones.Full suite: 13 failures, all in the documented pre-existing set (
TestProjectFilter*,TestTimeline*,TestBackfillTagsFromArea,TestParity*—projax.itemsfrozen post-Phase-6). Nothing new; none touch the board or views paths.Not verified by me: deploy rollout (healthz SHA == HEAD) — that follows the head's merge.
Shift Progress [shift-1]
Worker: cronus (role: inventor)
Duration: 4m
Status: progress
Slices C–E, G shipped (read-only three-source board)
Branch
mai/ritchie/12-coder-three-source, offmainatd690509. Read path only — Slice F (write-back) deliberately NOT started; it is the only slice that can mutate m's real data and it gets its own gated shift.Design doc corrected first
74c3120— m overruled the doc's premise that the Phase-7c merged list should be retired. His ruling separates two layers all of us had conflated:Within one project the merge is never exercised (one source → one kind of row) — moot, not wrong. But the cross-project daily board needs it: project A contributes CalDAV rows and project B Gitea rows into ONE board. Retiring it would have deleted the capability the board is being built for. §1.2 rewritten, Slice E re-scoped, all four of m's answers recorded as decisions in §8.
Slices
9bb9c29task_source+ strict parser + behaviour-neutral resolvercb45f04store.Task(read-only)efa5eb31aafddd8783f28C —
metadata.projax.task_source ∈ {gitea,caldav,native}. The load-bearing property is the resolver's fallback: an absent or unparseable declaration resolves via the pre-#12 implicit selector, so the field is behaviour-neutral by construction — deploying it cannot change any of the 66 live projects whether or not a backfill runs. Verified againstmbrian.*today: 27 no-links / 36 gitea-only / 2 caldav-only / 1 both = 66, zero carrying atask_source. Gitea-sourcing is never inferred, only declared.The write path is deliberately not in
UpdateInput/projaxBundleForUpdate— that bundle replaces every key it sends, so carryingtask_sourcethere would blank it on any unrelated edit whose caller forgot to project it in. That is the full-replace nulling trap that already hitRender,TagsandAliases(#7, #9). A narrowSetTaskSourcePATCHes only that key instead (theSetPinnedpattern).D —
gitea/donelabel.gofinally writes the non-uniform done-label vocabulary into code:status:donein m/projax+m/otto, baredonein m/mbrian+m/msbls.de, both in m/mAi. A hardcoded name returns 200 and silently no-ops. That has been relearned repo-locally four times; it is now a tested predicate. Also readscreated_atoff the wire so cards don't reshuffle on every touch, andDueis always nil — no invented deadlines from milestones, which is exactly why Q4 chosegroup_by=status.E — the source of truth routes through the resolved
task_source, and the XOR is enforced at the write (the create endpoint refuses a native task on a non-native project), not merely respected by the UI that usually hides the form. §6.3 integrity notice reports orphaned native tasks rather than hiding them.TestDisplayMergeSurviveshands the merge all three sources at once, so a future "simplify it away" fails loudly and says why.G — found and fixed the bug that made the whole degrade path impossible:
issueCache.Getevicted expired entries, so the stale entry was already gone by the time the refetch it triggered failed — silently turning every outage into an empty list. Expired entries are now retained and served as last-known-good with their age. Three states never collapsed: fresh (incl. honestly empty) / stale (real rows, always labelled "as of HH:MM") / unavailable ("no cached data — this does not mean there are no tasks"). Writes are blocked while stale or down. Also fixed a real bug from D: the row template's else-branch would have rendered structurally broken mBrian write forms for Gitea rows, which carry noNodeID.Not done, deliberately
The one-shot backfill onto all 66 live projects was not run. The resolver's fallback already makes the field behaviour-neutral, so the backfill buys no correctness — it would only make the implicit explicit, at the cost of 66 writes to m's live mBrian nodes on deploy. That trade wants sign-off rather than a worker's judgement.
SetTaskSourceis the mechanism if it's wanted.Consequence worth stating plainly: because nothing is declared yet, every project still resolves to its pre-#12 source, so the board shows no Gitea tasks until m declares a source on a project. That is the design working as approved (§3.3: opt in per project, nobody's board changes under them), not a gap — and
8783f28is the affordance that makes opting in possible.Verification
go build ./...+go vet ./...clean. New tests for each slice; the Slice C resolver tests were verified to fail against a deliberately broken resolver before being accepted. Full suite shows exactly the 13 known pre-existing failures (TestProjectFilter*,TestTimeline*,TestBackfillTagsFromArea,TestParity*) — no new ones.Not verified: no deploy, no live-prod check (branch is unmerged). Pre-existing gofmt drift in
web/gitea.goand other files predates this work and was left alone rather than creating noisy reformatting diffs.— ritchie (coder)