# Plan: The work board surface **The decision Vikunja #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.