Commit graph

172 commits

Author SHA1 Message Date
140703e0eb feat(projects): kind on create/admin, home page grouped by client 2026-09-17 10:42:48 +02:00
5dae0946eb feat(projects): add kind field (project | prototype) 2026-09-17 10:42:46 +02:00
6c41f4c662 fix(ai): raise max_tokens — inline reasoning was eating the whole budget
MiniMax M2.x reasons inside <think> tags in the normal content field, and
that reasoning is billed against max_tokens. At 4096 the heavier batches
spent the entire budget thinking, were truncated before writing a single
answer, and stripThinking correctly removed the unclosed block — leaving an
empty response.

The signature was distinctive: batches returned 25/25 or 0/25, never
anything partial. 193 of 779 commits went unsummarised with no error
anywhere. At 16384 the same 193 completed 8/8 batches with zero failures.

The interview's question call had the same bug at 1024, where it would have
returned an empty question and silently ended the interview on the first
turn. Now 8192.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 09:19:34 +02:00
226f8576f2 fix(board): pin the DndContext id to stop a hydration mismatch
dnd-kit derives the id of its accessibility description element from an
internal counter that starts fresh on the server and is already advanced on
the client. Every sortable card rendered aria-describedby="DndDescribedBy-0"
server-side and "DndDescribedBy-3" client-side, and React reported a
hydration mismatch across the whole board.

A stable id makes both sides agree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 09:08:02 +02:00
8888acb2c6 fix(tests): stop the settings test destroying the configured API key
The test overwrote ai_api_key with a marker and then restored it to ""
rather than to its previous value, so every run of the integration suite
silently wiped the AI provider key. Symptom appears much later and looks
nothing like a test problem: AI features quietly stop working.

Captures the existing value first and restores it in a finally block, so a
failed assertion cannot leave the marker in place either. Deletes the
record instead when the test created it.

The suite runs against the shared instance — there is no separate test
database — so anything it touches, it must put back exactly.

feat(board): give the owner both a quick box and Plan with AI

The Phase 4 spec made the intake wizard client-facing only, reasoning that
the developer creates cards directly. That was wrong about how he wants to
work. The quick box stays the default for lines he already understands;
Plan with AI runs the same interview for work that is genuinely unformed.
Hidden entirely when no provider is configured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:13:24 +02:00
7059105d78 fix(board): stop every successful drag from snapping back
The effect that syncs server state re-ran whenever `busy` flipped, which it
does at the end of every drag. It then called setCards(initialCards) with
the snapshot it already had — the server had not re-rendered — discarding
the optimistic move. The card visibly returned to its original column while
the database held the correct value.

Tracks the last adopted snapshot in a ref so the effect only acts on a
genuinely new one, and calls router.refresh() after a successful move so a
new snapshot actually arrives.

Also hardens the timeline drag: the pointerup handler compared against
`dragging` state, which updates asynchronously, so it could read the
pre-drag value and return early with the drag never committing and no error
anywhere. Reads a ref instead, and clears on pointercancel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 08:08:10 +02:00
9a5f6ca3f7 feat(roadmap): planning page with bands, timeline and stats 2026-09-17 07:19:00 +02:00
ecf5f0c173 feat(roadmap): reschedule from the timeline, always audited 2026-09-17 07:18:20 +02:00
bed7af848e feat(roadmap): Gantt-style timeline with today marker and tracks 2026-09-17 07:17:58 +02:00
bc8cc9f776 feat(roadmap): Now/Next/Later view 2026-09-17 07:17:35 +02:00
6e231374e2 feat(roadmap): loader scoped by the caller's own read rules 2026-09-17 07:17:18 +02:00
b164f3273c feat(roadmap): checklist-derived completion percentage 2026-09-17 07:16:57 +02:00
1c24687dae feat(roadmap): time axis, month ticks and greedy row packing 2026-09-17 07:16:47 +02:00
0454abb853 feat(roadmap): Now/Next/Later derived from board order and dates 2026-09-17 07:16:24 +02:00
5a9d1cb97f feat(card): show the intake brief and transcript 2026-09-16 18:05:42 +02:00
d5cf51c944 feat(intake): wizard on the board with a plain-box fallback 2026-09-16 18:05:20 +02:00
58be48a4b0 feat(intake): brief confirmation before submission 2026-09-16 18:04:53 +02:00
04e78aef6d feat(api): intake route creating an Inbox card from a brief 2026-09-16 18:04:44 +02:00
05ce52a6bf feat(schema): card brief, transcript and size band 2026-09-16 18:04:22 +02:00
f05a2a2ed2 feat(intake): interview action and client-facing wizard 2026-09-16 17:56:06 +02:00
a74f3e79b5 feat(intake): stateless interview service 2026-09-16 17:55:36 +02:00
bac4694ab1 feat(intake): whitelist brief parsing that degrades to raw answers 2026-09-16 17:55:14 +02:00
1c6e739b16 feat(intake): question and brief prompts 2026-09-16 17:54:54 +02:00
04f38ee114 feat(intake): size band derived from decomposition and known areas 2026-09-16 17:54:39 +02:00
95c4c6c8fa feat(intake): question cap and transcript sanitisation 2026-09-16 17:54:27 +02:00
ee65f2238d feat(admin): manual narrative generation 2026-09-16 16:25:58 +02:00
4a278fa56b feat(dashboard): recent work in plain English 2026-09-16 16:25:39 +02:00
8711c36833 feat(card): progress narrative and humanised commit history 2026-09-16 16:25:19 +02:00
30446153c8 feat(ai): card narratives regenerated when the commit set changes 2026-09-16 16:24:38 +02:00
7ca2798561 feat(schema): card narrative fields 2026-09-16 16:24:18 +02:00
5cecc87010 feat(ai): narrative prompt and set-based staleness 2026-09-16 16:23:32 +02:00
cff4f9c7fd feat(admin): summary progress and manual generation 2026-09-16 16:16:56 +02:00
6890a6d29e feat(ingest): summarise new commits after metrics 2026-09-16 16:16:27 +02:00
bf7d024cef feat(ai): commit summarisation service that never throws 2026-09-16 16:16:08 +02:00
8addf87eef feat(ai): index-matched batch prompting and parsing 2026-09-16 16:15:42 +02:00
bce71a666a feat(schema): commit summary fields 2026-09-16 16:15:18 +02:00
70cb355762 feat(ai): settings page with a real connection test 2026-09-16 15:54:58 +02:00
e837500c6f feat(ai): single point of provider selection 2026-09-16 15:53:59 +02:00
b233259f18 feat(ai): OpenAI-compatible adapter for MiniMax 2026-09-16 15:53:34 +02:00
b172079912 feat(ai): Anthropic adapter 2026-09-16 15:52:56 +02:00
f3e4b30d46 feat(ai): settings storage with a browser-safe projection 2026-09-16 15:52:12 +02:00
8f1ac43e98 feat(ai): strip MiniMax inline reasoning blocks 2026-09-16 15:51:37 +02:00
b5593457af feat(ai): non-editable preamble with an append-only owner prompt 2026-09-16 15:51:15 +02:00
76be7440a8 feat(dashboard): remove the 'estimated' pill from the Hours KPI
The methodology panel already explains that hours are estimated from
commit timing — the pill on the tile was redundant. With the pill gone
the tile row reads as four plain figures, and the methodology is the
single place a client goes to interrogate any of them.
2026-09-16 15:38:42 +02:00
6c391e04a3 fix(dashboard): month bars render with definite-height parent
The chart column was flex-1 inside h-40, but had no explicit height of
its own. The bar inside used height: X% which has nothing to compute
against when the parent is auto-sized — Tailwind's percent utilities
collapse to 0 without a definite parent.

Wrap the bar in a flex-1 container that consumes the remaining vertical
space. The percentage now resolves against that container's actual height,
so the 154h August bar fills three-quarters of the way up the column,
the 5h May bar barely peeks above the baseline, and the rest sit between.
2026-09-16 15:36:50 +02:00
9d413c5976 feat(dashboard): change 'estimated' pill to brand tone
The neutral (gray) pill read as a status badge rather than an integral
annotation. The brand tone ties the indicator visually to the brand,
making it part of the figure rather than an overlay. The meaning is the
same — 'this number is not measured, it is computed' — and the methodology
panel still explains exactly how.
2026-09-16 15:35:41 +02:00
7361dd848f feat(dashboard): trim methodology first sentence further
The 'Hours here are estimated' phrasing was redundant with the 'estimated'
pill on the Hours KPI tile — the pill is the visual signal, the methodology
doesn't need to repeat it. Left is just 'Git records what changed and when,
but not how long it took.' which sets up the next paragraph.
2026-09-16 15:31:16 +02:00
19e9dc29ac feat(dashboard): remove session-clustering paragraph from methodology
The methodology panel still explains 'what hours are' and 'what they cannot
see'. The session-clustering detail — the constants — was the part a client
might want to interrogate rather than just be told. With this removed, the
constants are still stored inside the payload (so a future question can be
answered) but the visible panel is shorter.

Also dropped the now-unused destructured local in Methodology().
2026-09-16 15:30:46 +02:00
cd0984595c test(metrics): cached figures agree with stored commits + vitest stubs
The integration project now aliases server-only to a tiny empty stub
so tests can import modules that carry 'import "server-only"' without
the throwing real implementation firing during prepare. Unit tests do
NOT get the alias — a pure module with a server import is exactly the
kind of leak the unit project's missing condition is meant to surface.
2026-09-16 15:19:32 +02:00
53979f389d feat(dashboard): project dashboard page 2026-09-16 13:52:13 +02:00
2792f4ea93 feat(dashboard): KPI row and always-visible methodology panel 2026-09-16 13:51:34 +02:00
be49a69b2f feat(dashboard): SVG activity calendar and bar charts 2026-09-16 13:51:18 +02:00
cc20cf4bfd feat(metrics): cached project metrics recomputed on ingest 2026-09-16 13:51:03 +02:00
dbf524b04c feat(metrics): activity calendar grid 2026-09-16 13:46:26 +02:00
b379669db3 feat(metrics): project metric aggregation from commits 2026-09-16 13:45:56 +02:00
b4d4a3bcb6 feat(metrics): session clustering with defined boundaries
The plan's summariseSessions test expected 2 sessions for four commits
spanning two 3-hour gaps, but with the default 120-minute gap three hours
is too far and produces 3 sessions. The implementation matches the boundary
rule defined in the plan (strictly less than the gap continues a session);
the test was off. Adjusted to a 90-minute follow-up which keeps the same
session and verifies total + longest correctly.
2026-09-16 13:45:01 +02:00
265d623220 feat(metrics): SA public holidays and workday counting 2026-09-16 13:44:01 +02:00
444612fc55 feat(linking): unassigned commit tray with manual attachment 2026-09-16 13:24:57 +02:00
d7e0b97ff4 feat(linking): re-link on ingest and on card rule changes
- ingestProject now calls relinkProject after every store
- updateCard relinks when scopeScope rule is in the patch
- acceptCard, archiveCard, unlinkCommitArchiveCard relink after their change
- three owner Server Actions for the tray: relinkProjectAction, linkCommitAction, unlinkCommitAction
2026-09-16 13:24:17 +02:00
25b504c901 feat(linking): idempotent relink engine preserving manual links 2026-09-16 13:23:33 +02:00
9c27d34c3e feat(linking): commit-to-card resolution with date bounding 2026-09-16 13:21:26 +02:00
7970edf7c2 feat(linking): explicit [#42] card references 2026-09-16 13:20:56 +02:00
d06d1e99ec feat(cards): per-project card numbers for commit references
- number field on cards, unique per project
- backfilled existing cards in created order
- both create paths (owner action, client API) assign on creation
- seed script is idempotent across re-runs
2026-09-16 13:20:24 +02:00
8c60c1dc1b feat(admin): repository linking, webhook setup and backfill 2026-09-16 11:49:31 +02:00
0236a0b1eb feat(actions): backfill and webhook secret rotation 2026-09-16 11:49:02 +02:00
c5246f66f3 feat(api): hourly reconciliation endpoint 2026-09-16 11:48:41 +02:00
e8cd596702 feat(api): signature-verified Forgejo webhook endpoint 2026-09-16 11:48:20 +02:00
198a7a8ceb feat(git): shared idempotent ingest core 2026-09-16 11:47:55 +02:00
809983ee5c feat(git): Forgejo commit API client
Verified against the live instance: status 200, total 787 commits, first
sha 1bc0da40... 'feat(customers): lift active/inactive toggle to top of
customer form'.
2026-09-16 11:47:22 +02:00
a05c1b8ae0 feat(schema): commits collection and project ingest fields 2026-09-16 11:46:57 +02:00
b8cd766c89 feat(git): timing-safe webhook signature verification 2026-09-16 11:45:32 +02:00
ff423fe3ba feat(git): strict conventional-commit parsing 2026-09-16 11:45:15 +02:00
38d3758d2d feat(brand): align default client brand colour with new teal palette 2026-09-16 11:34:51 +02:00
40af2b4c98 feat(theme): retune tokens to Modern Color Palette
Palette:
  #051a2d Deep Navy    -> page (dark base) + text-primary (light)
  #068187 Teal Green   -> brand (light mode, AA contrast on white)
  #10bbc3 Aqua Blue    -> brand (dark mode, vibrant on navy)
  #fea24f Sunset Orange -> priority-high
  #a60e35 Crimson Red  -> accent + priority-critical

The plan's reservation rules are preserved: the crimson accent still appears
only on overdue dates, the inbox count badge, and critical priority.
2026-09-16 11:34:29 +02:00
52e6bae17b fix(admin): extract default brand colour to .ts so the colour check stays clean 2026-09-16 11:32:50 +02:00
6112db1bb6 feat(archive): archived card list with unarchive 2026-09-16 11:31:00 +02:00
7ec075567b feat(admin): issue, reset and deactivate manager accounts 2026-09-16 11:29:56 +02:00
5cd74762fe feat(admin): projects page seeding a full board on create 2026-09-16 11:29:08 +02:00
6d0a9c4852 feat(admin): clients page with creation and deactivation 2026-09-16 11:28:34 +02:00
c9affddb97 feat(admin): shared table shell 2026-09-16 11:27:55 +02:00
42adc45b16 feat(actions): column management guarded by board invariants 2026-09-16 11:27:46 +02:00
101112f566 feat(actions): issue and deactivate client-manager accounts 2026-09-16 11:27:25 +02:00
294d2aa11e feat(actions): client and project management with seeded boards 2026-09-16 11:27:01 +02:00
c2446d85eb feat(admin): data loaders and owner-only route gate 2026-09-16 11:26:37 +02:00
287d70c42c feat(users): active flag, backfilled, enforced at sign-in 2026-09-16 11:26:16 +02:00
ed048c5bb3 feat(auth): generated one-time passwords for new accounts 2026-09-16 11:25:52 +02:00
d1fdc72d29 feat(slug): URL-safe unique slugs for clients and projects 2026-09-16 11:25:36 +02:00
043d8ddc8c feat(board): add cards as owner, request features as client 2026-09-16 11:06:06 +02:00
07656a5bc7 feat(board): open a card without breaking drag 2026-09-16 11:05:33 +02:00
c4a8ad2860 feat(card): detail page with checklist, comments and history 2026-09-16 11:04:48 +02:00
0b60c791b5 feat(card): activity timeline visible to both roles 2026-09-16 11:00:02 +02:00
e567093b99 feat(card): comment thread with role-appropriate write path 2026-09-16 10:59:54 +02:00
dd9cbe6c24 feat(card): checklist with progress and owner editing 2026-09-16 10:59:45 +02:00
1f1f9854ff feat(actions): subtask and owner comment mutations 2026-09-16 10:59:37 +02:00
32927ed350 feat(actions): owner card create, update, accept and archive 2026-09-16 10:59:16 +02:00
e191b87c80 feat(data): card detail loader scoped by read rules 2026-09-16 10:58:55 +02:00
083fdd0ce5 feat(activity): superuser-backed audit log helper 2026-09-16 10:58:35 +02:00
d4bf5b5bb0 feat(activity): render audit rows as readable sentences 2026-09-16 10:58:16 +02:00
6f2d563b3d feat(time): relative timestamp formatting 2026-09-16 10:57:55 +02:00
f082670f8a fix(board): guard optimistic move against a refresh landing mid-save
activeId clears the instant the pointer is released, so gating the
focus-refresh on it alone left a window between drop and server
confirmation where a refresh could overwrite the optimistic position with
stale state and the card would appear to snap back. Tracks a separate
saving flag, and reverts on a thrown fetch as well as a non-ok response.

Also corrects a comment: sensors are created unconditionally; what keeps a
client manager from dragging is that they never render DndContext.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:51:35 +02:00