The first write path. Read, forecast, and calibration were all real; now you can
*manage* CommiTea from CommiTea. Estimates/priority are exclusive label axes, so
a change is a label swap — proposed, approved, then written. Nothing is assumed.
core (@commitea/core):
- planIssueChange(current, change): pure diff planner — swaps the est/*|p/* axis,
clears on null, dedups a doubled axis; returns the resulting label set + a
before/after diff + noop flag. describeChange() renders "est/2d → est/5d".
- request() seam extended for writes (method/body, JSON, 204). client gains
listLabels() (name→id) and setIssueLabels() (PUT /issues/{n}/labels).
app:
- main bridge gitea:applyChange — resolves plan.labels → ids (cached, refetch on
miss), PUTs, returns the plan + fresh issue. Token never leaves main.
- preload + global.d.ts expose applyChange; useBacklog returns a refetch so a
write re-reconciles the board + forecast.
- Issue screen: an Adjust button (shown only when configured) opens a
propose-approve Dialog — estimate/priority pickers, live "est/3d → est/8d"
consequence, Apply/Cancel. AppShell wires it, reflects new labels on the open
issue immediately, and refetches.
Verified: 83 core tests green (7 apply-changes + 2 client-write new), desktop
typecheck clean, 14 fixture e2e green. Live spec exercises propose + CANCEL (no
mutation); the real PUT was verified once manually (change #2 est/3d→est/8d→200,
reverted clean). Icon: pencil (no sliders-horizontal in the set).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fill the board's Steeping / In-review columns (and the calibration actuals)
from real gitea timeline events, replacing the three-column-only v0.
core (@commitea/core):
- inferLifecycle(issue, events, asOf): five-column inference — closed → done;
open PR ref → review; commit ref → steeping; any triage signal → triage;
else diagnosis. Earliest event of each kind fixes the stage timestamp.
- Derives actualWorkingDays (work-start → close) — the estimate-vs-actual the
calibration fit (D3) learns from — and steepingDays (first commit → now) for
the board age badge.
- workingDaysBetween(): whole Mon–Fri days in [start, end), day-granular.
- normalizeTimeline() + client.getIssueTimeline(): map gitea's raw timeline
(label/milestone → triage, commit_ref → commit, pull_ref → pull, close,
reopen), drop the rest. Paginated.
app:
- reconcile now fetches every issue's timeline and returns it keyed by number;
threaded through the bridge → useBacklog → board/focus.
- issuesToBoardColumns + scheduleFocus run inferLifecycle: real Steeping/In-review
columns, steeping-age `days` badge, focus-card steeping badge.
Known refinement: gitea's pull_ref fires on any PR mention, so an issue merely
referenced in a PR body can read as In-review; distinguishing closing refs from
mentions needs the PR link's state (later). Re-opening multi-segment actuals
also deferred.
Verified: 63 core tests green (15 lifecycle, incl. workingDaysBetween + the five
transitions), desktop typecheck clean, 14 fixture e2e green, live spec asserts
the board's Done column is populated from real events.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CommiTea now recommends its own next unit of work from the live backlog.
- @commitea/core: `schedule()` — dependency topo-sort with priority +
estimate tie-breaks, single serial capacity, cycle detection, and
critical-path marking; `selectFocus()` takes the top three. Pure,
deterministic; the LLM does none of this. +11 tests (39 in core).
Client gains `getIssueDependencies`.
- main: reconcile also fetches native issue dependencies for the open
scope and returns edges.
- renderer: `scheduleFocus()` maps real issues+deps→Now/Next/Later;
Focus renders scheduler output (fixture fallback when unconfigured).
v0 scope (each a later slice): single serial worker (per-person
capacity #8), point durations (Monte Carlo cone #10), estimate-only
(calibration #5). Verified: 14 e2e green (fixtures) + gated live spec —
the board shows the real 25 open + 9 closed, and Focus picks #2
ChangeSource (critical path) as Now. Screenshots confirmed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The app now displays its own live backlog instead of fixtures. First
end of the sync loop — the tap-root (#1) grows list reads and a read
path through the Electron main process.
- @commitea/core: client gains `listIssues` (paginated, PRs excluded)
and `listMilestones`; a `lifecycle-v0` mapper (closed→done,
labelled/milestoned→triage, bare→diagnosis — steeping/review await
event inference in P1-5). +10 unit tests.
- main: gitea bridge over IPC (token stays in main, never the renderer);
config from env / .env.local; gated off under COMMITEA_E2E so the
committed e2e stays on fixtures. Preload exposes the typed bridge.
- renderer: useBacklog() reconciles once on mount; issuesToBoardColumns
shapes real issues into The pot. Board takes optional real columns +
a loading state, falling back to demo fixtures when unconfigured.
Verified: 14 e2e green (fixture mode) + a gated live spec that launches
against the real repo — the board renders the actual 25 open + 9 closed
issues (screenshot). SQLite mirror + reconcile-on-a-timer + lifecycle
event inference are the next slices (#2/#3/#5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>