Files
Maven/docs/plans/15-board-surface.md

6.2 KiB

Plan: The work board surface

The decision V-431 asked for. Written 04-08-2026.

Verdict: build it, in a smaller shape than the task imagined. The board is worth moving out of the file. The intake form belongs on the /tasks page, not on the voice path. The argument is not built, now or later.

Why it is worth building

The reason is the one the task gives and it holds: company rules forbid pointing Claude at work repos, and Maven is the one assistant on the box that work material may reach. No telemetry, no cloud model, no third-party account. That is not a preference here, it is the whole permission.

The build is also small, because most of it landed already:

Piece Where State
task rows, dedupe, status lifecycle internal/store/migrations.go:149 and migration #15 done
capture from speech, urgency stripped router.ParseTaskCapture, router.TaskCaptureGrammar done
recite the list on request router.IsTaskListQuery done
a page to read and change the board /tasks in cmd/mavweb done
counting a shape without judging it internal/memory/behavior.go done, as precedent
a proposal he reads when he chooses /routines, the proposed-routine queue done, as precedent

tasks already carries status (candidate, open, done, dropped), due_ts, weight, source, evidence, ext_id and resolved_by. Three things are missing. It has no definition of done and no blocked-on. There is no way to edit a task after capture: SetTaskStatus moves the status and nothing writes text, date or weight again. And there is no grouping he controls, because order is computed by tasks.Rank alone.

Where the form lives, and why not voice

The task asks the form to refuse a capture with no definition of done. That refusal cannot live on the voice path, for two reasons.

The parked-state mechanism is binary. resolveConfirm in cmd/mavend/confirm.go answers yes or no against a slot with a 90-second life. Filling four fields over four turns is slot filling, which is a different mechanism and a new one. Nothing in the daemon does it today.

The definition of done is the worst possible field to dictate. It is the one string that has to be exact, because its whole purpose is to be unarguable later. Whisper transcribing a sentence of Russian work vocabulary is where exactness goes to die, and the capture path already had to strip a question mark that whisper invented.

So: voice captures a line and recites the list. The page is where a line becomes an item with a definition of done, a blocked-on and a date. A captured line lands as candidate and stays there until it is filled in, which is what candidate was for.

The refusal the task wants survives, moved: the page will not promote a candidate to open without a definition of done, the same way ParseTaskCapture will not file a marker with nothing after it. And the field must close on either outcome, so "it already works" counts as complete. A definition of done that only one result satisfies is a wish.

Does the stage-0 trick stretch

The task asks this before any shape is committed to. It was checked. The answer is partly.

TaskCaptureGrammar matches every utterance and lets ParseTaskCapture decide inside Build, keeping the intent at note and leaving the frozen seven-intent contract alone. That trick stretches to recite and to status change: both are a marker plus a referent, both are a lookup, and a status change is a small closed verb set over a list he can see. It does not stretch to intake, because intake is not one utterance, and it does not need to, because intake moved to the page.

One cost to name. Each such grammar matches everything and runs its parser on every turn, ahead of the resident model. Two more of them is fine. A dozen would make stage 0 a second router with no evaluation behind it, and at that point the frozen enum is the smaller problem.

What is not built: the argument

Not now and not later behind a flag. The task is right about why, and internal/memory/behavior.go already argued it for habits: a 1.7B asked whether evidence proves anything will agree fluently and launder a guess into a decision. A wrong claim about his work, stated confidently, is the most expensive kind of wrong Maven can be.

The line is the same line behaviour memory drew. She may count:

  • no state change in eleven days
  • blocked on a person, with no date
  • four of nine waiting on two people

Those are queries over rows. She may not assess whether a build proves anything, whether a blocker is real, or whether a task should be dropped.

Persona

A progress tracker is a nag by default, and "not a nag" is hard. The line is already drawn twice in the codebase and it is drawn the same way here:

  • A date he set becomes a reminder. He set it, so it is not her raising it.
  • A stall becomes a proposal he reads when he chooses, on a page, like /routines.
  • Ask what is on the board and she recites. She never opens with it.

The day plan is the place to watch. tickLoop.dayPlan reads calendar events, pending reminders and checklist facts, and it does not read tasks. Adding the board to the morning nudge is exactly the move that turns this into a nag, so the board goes on the page and into the answer when asked, and not into the unprompted morning message.

The build, as tasks

  1. Two columns on tasks: definition of done, and blocked-on. Blocked-on resolves through Nexus like any other person reference, because identity lives in Nexus.
  2. An edit path. Today a task is write-once except for its status, so the form has nothing to save into.
  3. /tasks grows the form: promote candidate to open only with a definition of done, set a date, set blocked-on. A date set here writes a reminder.
  4. A status-change grammar at stage 0, following TaskCaptureGrammar.
  5. Counted stall shapes on /tasks, phrased as counts. No assessment.

Note for whoever picks up 3: /tasks accepts its POST without the step-up gate, while /routines and /tools require a passkey. That was deliberate for capture. Adding an edit path is the moment to re-argue it, not to inherit it silently.

Each is separable and each is worth stopping after.