Kanban / daily-board view for projax — inventor: research prior art + what to REUSE first #12

Open
opened 2026-07-21 08:02:25 +00:00 by mAi · 4 comments
Collaborator

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:

  1. What already exists to reuse — inside projax first (its current data model: mBrian [project] nodes, child_of / projax-* scoped edges, the existing views/CLI), then the wider stack m already runs (the otto PWA Today band/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.
  2. The shape — how a Kanban / daily-board maps onto projax's existing project + task model without a parallel store: columns = status, cards = projects/tasks, a per-day "daily board" slice. Keep it aligned to projax's mBrian-backed model, don't invent a second source of truth.
  3. Board vs. daily-board — clarify whether m wants one project-level kanban, a daily working board, or both as views over the same data.

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.

## 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: 1. **What already exists to reuse** — inside projax first (its current data model: mBrian `[project]` nodes, `child_of` / `projax-*` scoped edges, the existing views/CLI), then the wider stack m already runs (the otto PWA `Today` band/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. 2. **The shape** — how a Kanban / daily-board maps onto projax's existing project + task model without a parallel store: columns = status, cards = projects/tasks, a per-day "daily board" slice. Keep it aligned to projax's mBrian-backed model, don't invent a second source of truth. 3. **Board vs. daily-board** — clarify whether m wants one project-level kanban, a daily working board, or both as views over the same data. ## 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.
mAi self-assigned this 2026-07-21 08:02:25 +00:00
Author
Collaborator

DESIGN READY FOR REVIEW — Kanban / daily-board

Design doc: docs/plans/kanban-daily-board.md
Commit: 4aa93456c0
Branch: mai/atlas/issue-12-kanban-daily


Headline: 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.goBuildKanbanBoard, pure function
  • web/templates/tree_kanban.tmpl — board markup + HTMX chip strip
  • web/static/style.css:232-261.kanban-board / .kanban-column / .kanban-card

So 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:

Fact Count
projax projects (live) 76
status=active 61
mBrian-native task nodes 0
Projects linked to a CalDAV task list 4 of 76
Saved views (projax.views) 0

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.views table 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:

Project board Daily board
Card = a project a task
Columns = status due bucket (overdue/today/tomorrow/week/no-due)
Today ships already doesn't exist

The insight that keeps this cheap: a daily board is a kanban whose group_by is the due-date bucket — and classifyDue in web/dashboard.go already 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.Task shape over both backends, the saved-views chassis, and even mbrian.edges.sort_order for drag persistence (no migration needed).

Outside projax: the otto PWA Today band (otto#405) turned out to be not reusable as code — bespoke JSX-to-string + imperative DOM in mAi/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's classifyDue lacks.

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.svelte and paliad's reorderViaDnd both 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)

  • Step 0 — task coverage. Decide where tasks come from for the 72 unlinked projects. Gates everything below. This is your decision, not a coding task.
  • A — create the board saved view so the existing kanban is findable in the sidebar. Near-zero code.
  • B — generalise the board engine over a KanbanCard shape so items and tasks feed one engine (avoids a second kanban).
  • C — task cards + group_by=due; extract classifyDue into a shared home so dashboard and daily board can't drift.
  • D — the today saved view: the daily board proper.
  • E (optional) — drag to change column, hand-rolled HTML5 DnD. Write path already exists (TaskWriter.SetTaskStatus / SetTaskDue).
  • F (optional) — roll mBrian tasks into the dashboard Tasks card (standing Phase 7 follow-up; only matters if Step 0 picks the mBrian path).

Open questions for you

  1. Step 0 — where do tasks come from? Wire the remaining 72 projects to CalDAV lists, or start using mBrian-native tasks? This gates everything.
  2. Did the views system land wrong? Zero saved views after two phases is a signal.
  3. Is drag-and-drop actually wanted, or is read-only + existing inline edit enough?
  4. Daily board default scope — all areas, or work by default?

Nothing implemented, per the gate.

## DESIGN READY FOR REVIEW — Kanban / daily-board Design doc: `docs/plans/kanban-daily-board.md` Commit: https://mgit.msbls.de/m/projax/commit/4aa93456c0f3e74ae9c829648e2d88de30d4deed Branch: `mai/atlas/issue-12-kanban-daily` --- ### Headline: 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 function - `web/templates/tree_kanban.tmpl` — board markup + HTMX chip strip - `web/static/style.css:232-261` — `.kanban-board` / `.kanban-column` / `.kanban-card` So 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: | Fact | Count | |---|---| | projax projects (live) | 76 | | …`status=active` | 61 | | **mBrian-native task nodes** | **0** | | Projects linked to a CalDAV task list | **4** of 76 | | Saved views (`projax.views`) | **0** | 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.views` table 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**: | | Project board | Daily board | |---|---|---| | Card = | a project | a task | | Columns = | `status` | due bucket (overdue/today/tomorrow/week/no-due) | | Today | **ships already** | doesn't exist | The insight that keeps this cheap: **a daily board is a kanban whose `group_by` is the due-date bucket** — and `classifyDue` in `web/dashboard.go` already 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.Task` shape over both backends, the saved-views chassis, and even `mbrian.edges.sort_order` for drag persistence (no migration needed). Outside projax: the otto PWA `Today` band (otto#405) turned out to be **not reusable as code** — bespoke JSX-to-string + imperative DOM in `mAi/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's `classifyDue` lacks. **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.svelte` and paliad's `reorderViaDnd` both 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) - **Step 0 — task coverage.** Decide where tasks come from for the 72 unlinked projects. **Gates everything below.** This is your decision, not a coding task. - **A** — create the `board` saved view so the existing kanban is findable in the sidebar. Near-zero code. - **B** — generalise the board engine over a `KanbanCard` shape so items *and* tasks feed one engine (avoids a second kanban). - **C** — task cards + `group_by=due`; extract `classifyDue` into a shared home so dashboard and daily board can't drift. - **D** — the `today` saved view: the daily board proper. - **E** *(optional)* — drag to change column, hand-rolled HTML5 DnD. Write path already exists (`TaskWriter.SetTaskStatus` / `SetTaskDue`). - **F** *(optional)* — roll mBrian tasks into the dashboard Tasks card (standing Phase 7 follow-up; only matters if Step 0 picks the mBrian path). ### Open questions for you 1. **Step 0 — where do tasks come from?** Wire the remaining 72 projects to CalDAV lists, or start using mBrian-native tasks? This gates everything. 2. **Did the views system land wrong?** Zero saved views after two phases is a signal. 3. **Is drag-and-drop actually wanted**, or is read-only + existing inline edit enough? 4. **Daily board default scope** — all areas, or `work` by default? Nothing implemented, per the gate.
mAi added the
status:needs-review
label 2026-07-21 08:08:02 +00:00
Author
Collaborator

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 — a board saved view is seeded on first run (store.SeedDefaultViews, called from cmd/projax/main.go after migrations).

  • Goes through store.CreateView, not a hand-INSERT — slug format check, reserved-slug list, JSON probe and server-assigned sort_order all apply.
  • view_type=kanban lives inside filter_json (the Phase 5j Q2 encoding — there is no view_type column); group_by=status is its own column. Icon box, from the existing 7-key registry; no new icon added.
  • Fires only when projax.views is 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.
  • board is 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

cc7dbf7BuildKanbanBoard took []*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; BuildKanbanBoard is a thin wrapper supplying ItemCard.
  • store.Task is 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=kanban body 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 ./... and go 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.items frozen 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.

## 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`](https://mgit.msbls.de/m/projax/commit/95ce30a) — a `board` saved view is seeded on first run (`store.SeedDefaultViews`, called from `cmd/projax/main.go` after migrations). - Goes through `store.CreateView`, **not** a hand-INSERT — slug format check, reserved-slug list, JSON probe and server-assigned `sort_order` all apply. - `view_type=kanban` lives **inside** `filter_json` (the Phase 5j Q2 encoding — there is no `view_type` column); `group_by=status` is its own column. Icon `box`, from the existing 7-key registry; no new icon added. - Fires **only when `projax.views` is 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. - `board` is 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`](https://mgit.msbls.de/m/projax/commit/cc7dbf7) — `BuildKanbanBoard` took `[]*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; `BuildKanbanBoard` is a thin wrapper supplying `ItemCard`. - `store.Task` is **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=kanban` body 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 ./...` and `go 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.items` frozen 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.
Author
Collaborator

Shift Progress [shift-1]

Worker: cronus (role: inventor)
Duration: 4m
Status: progress

## Shift Progress [shift-1] **Worker:** cronus (role: inventor) **Duration:** 4m **Status:** progress
Author
Collaborator

Slices C–E, G shipped (read-only three-source board)

Branch mai/ritchie/12-coder-three-source, off main at d690509. 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:

  • source of truth = a per-project property. The XOR (exactly one source per project) applies here.
  • display = a property of the view, free to aggregate. The merge stays.

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

Commit What
C 9bb9c29 explicit task_source + strict parser + behaviour-neutral resolver
D cb45f04 Gitea issues adapted into store.Task (read-only)
E efa5eb3 source-scoped source of truth, display merge kept
G 1aafddd per-source stale markers, never silent-omit
+ 8783f28 declare a project's source from the detail page

Cmetadata.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 against mbrian.* today: 27 no-links / 36 gitea-only / 2 caldav-only / 1 both = 66, zero carrying a task_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 carrying task_source there would blank it on any unrelated edit whose caller forgot to project it in. That is the full-replace nulling trap that already hit Render, Tags and Aliases (#7, #9). A narrow SetTaskSource PATCHes only that key instead (the SetPinned pattern).

Dgitea/donelabel.go finally writes the non-uniform done-label vocabulary into code: status:done in m/projax+m/otto, bare done in 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 reads created_at off the wire so cards don't reshuffle on every touch, and Due is always nil — no invented deadlines from milestones, which is exactly why Q4 chose group_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. TestDisplayMergeSurvives hands 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.Get evicted 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 no NodeID.

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. SetTaskSource is 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 8783f28 is 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.go and other files predates this work and was left alone rather than creating noisy reformatting diffs.

— ritchie (coder)

## Slices C–E, G shipped (read-only three-source board) Branch `mai/ritchie/12-coder-three-source`, off `main` at `d690509`. 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`](https://mgit.msbls.de/m/projax/commit/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: - **source of truth** = a per-project property. The XOR (exactly one source per project) applies *here*. - **display** = a property of the view, free to aggregate. **The merge stays.** 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 | | Commit | What | |---|---|---| | **C** | [`9bb9c29`](https://mgit.msbls.de/m/projax/commit/9bb9c29) | explicit `task_source` + strict parser + behaviour-neutral resolver | | **D** | [`cb45f04`](https://mgit.msbls.de/m/projax/commit/cb45f04) | Gitea issues adapted into `store.Task` (read-only) | | **E** | [`efa5eb3`](https://mgit.msbls.de/m/projax/commit/efa5eb3) | source-scoped source of truth, display merge kept | | **G** | [`1aafddd`](https://mgit.msbls.de/m/projax/commit/1aafddd) | per-source stale markers, never silent-omit | | + | [`8783f28`](https://mgit.msbls.de/m/projax/commit/8783f28) | declare a project's source from the detail page | **C** — `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 against `mbrian.*` today: 27 no-links / 36 gitea-only / 2 caldav-only / 1 both = 66, **zero** carrying a `task_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 carrying `task_source` there would blank it on any unrelated edit whose caller forgot to project it in. That is the full-replace nulling trap that already hit `Render`, `Tags` and `Aliases` (#7, #9). A narrow `SetTaskSource` PATCHes only that key instead (the `SetPinned` pattern). **D** — `gitea/donelabel.go` finally writes the **non-uniform done-label vocabulary into code**: `status:done` in m/projax+m/otto, bare `done` in 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 reads `created_at` off the wire so cards don't reshuffle on every touch, and `Due` is **always nil** — no invented deadlines from milestones, which is exactly why Q4 chose `group_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. `TestDisplayMergeSurvives` hands 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.Get` **evicted** 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 no `NodeID`. ### 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. `SetTaskSource` is 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 `8783f28` is 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.go` and other files predates this work and was left alone rather than creating noisy reformatting diffs. — ritchie (coder)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/projax#12
No description provided.