Source weighting, grow-first fill & project consolidation#3
Merged
Conversation
Rework day classification so observed activity owns the day and trends only top it up — fixing the symptom where almost everything came from trends and real blocks were lost. - Source priority calendar > commits > browser > Linear > trends, with trends strictly subordinate (fill only, never relabel/evict observed work). - Deterministic trend engine (computeTrendPatterns): weeks present, cadence, avg duration and historical share computed in TS; the LLM only labels, never invents (hybrid — see ADR-0004, amends ADR-0002). - Grow-first fill in PackDayUseCase: distribute the 8h gap proportionally across observed project blocks (capped at historical average), then fill only with strong recurring patterns (>=3 of 4 weeks + cadence) for no-activity projects. - Project consolidation (consolidateByProjectService): fold same project+service blocks into one Project block, preserving individual PRs/commits in the note; different services stay separate (billing); meetings/fill excluded. - Linear becomes a first-class source: completed issues not referenced by a commit become their own classifiable blocks; covered ones stay absorbed. - Stable repo->project cache key (mappingCacheKey): strip the @hh:mm session suffix so commit classification stops re-guessing the repo's project daily. - Prompt rewrite: source priority, hard-evidence relatedness gate (name the evidence or don't couple), and the app now owns sizing/fill. Glossary updated (CONTEXT.md): Source, Absorption, Project block, Trends, Strong recurring pattern, Fill target. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Coverage Report
File Coverage |
…t real times Two defects surfaced in review: morning meetings vanished while trend-fill re-invented meeting-named blocks, and surviving meetings were repacked to the wrong times. - Every accepted meeting now ALWAYS yields a block (GroupAndClassifyDayUseCase), independent of whether the LLM echoes its index. Falls back to the event title and cached project/service mapping, else stays unclassified — never silently dropped. Standalone browser items stay LLM-driven (the model may skip noise). - Meetings are anchored at their real calendar times again (PackDayUseCase), matching CONTEXT.md's Anchor definition. Concurrent meetings keep their times and overlap each other; the existing assignBlockColumns renders them side by side (Google-Calendar style). Movable work/fill flows around the union of existing entries + meeting spans; meetings count toward the 8h target. - Prompt: instruct the model to always return a block for every meeting item (project/service may be null) since calendar has highest priority. CONTEXT.md Meeting block entry updated with the guarantee + overlap rendering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The real reason morning meetings vanished: the calendar fetch's isAttending kept only 'accepted'/'tentative', so recurring team meetings left on 'needsAction' (attended but never RSVP'd) were filtered out before they ever became events — no downstream guarantee could recover them. Relax isAttending to keep everything except explicitly 'declined' meetings, matching the highest-priority status of the calendar source. Added a test for needsAction kept / declined dropped; CONTEXT.md wording updated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
assignBlockColumns now groups blocks into transitive overlap CLUSTERS and splits width only within a cluster, so a single overlapping meeting no longer narrows the whole day (the old global numCols flung overlappers to a far column). Each block carries `cols` (its cluster's column count); DayTimeline renders width = 100/cols with a 4px gutter between concurrent columns and a 3px vertical inset for Google-Calendar breathing room. Chosen from four mockups in docs/design/timeline-overlap-options.html (Option 1, split-within-band). Added a test asserting a localized overlap keeps non-overlapping blocks at full width. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Short blocks (15-min meetings) rendered a two-line layout that clipped at the min height. Render a single compact line (title + time inline, badge hidden) when a block is under 42px, and raise the content-height floor to 24px so one line always fits. Applies to concept and entry blocks. - The packer no longer deduplicates meeting blocks against booked entries — a meeting is never removed (calendar is highest priority). The day may exceed 8h; the user manually deselects blocks before booking. Non-meeting concepts are still deduped as before. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocks classification found but the packer couldn't place were silently lost.
They now surface in a right sidebar (compact-chips design) instead of vanishing.
Domain:
- PackDayUseCase.executeWithLeftovers returns { placed, leftovers }. Leftovers =
movable work that overflows past dayEnd (reason 'overflow') + LLM candidates the
day didn't need (reason 'suggestion', deduped by project+service). Meetings are
never leftovers. execute() stays a placed-only delegator for existing callers.
- ClassifiedBlock gains unplaced + leftoverReason; ProcessDayUseCase persists
placed + leftovers together (UI routes by the flag).
UI:
- New LeftoverSidebar (Design 2): collapsible rail with count badge, auto-opens
when leftovers exist, dense chips with hover actions Toevoegen / Boek / Negeer
and an "Alles +" bulk add. Confidence colors + reason labels match the app.
- WeekPage routes placed→DayTimeline, unplaced→sidebar, and wires the actions:
add appends to the day, book routes through the booking modal, dismiss removes.
Glossary: CONTEXT.md "Leftover block". Design options kept in
docs/design/leftover-sidebar-options.html (Option 2 chosen). 1654 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Time-tracking days were being built mostly from trends (historical bookings): real meeting/commit/browser blocks got lost or mislabelled, unrelated things were coupled, and the calendar — which should be authoritative — barely showed up. This PR reworks classification, packing, and the timeline UI so that observed activity owns the day and trends only top it up, the calendar is the highest-priority source, and nothing the classifier finds is silently dropped.
Designed across several grilling sessions; decisions live in
CONTEXT.md(glossary) anddocs/adr/0004-*(amends ADR-0002). Two design explorations are recorded indocs/design/.Core model
computeTrendPatterns) — weeks present, cadence, avg duration, historical share computed in TS; the LLM only labels candidates, never invents them (hybrid; see ADR-0004).PackDayUseCase) — the 8h gap is filled by growing observed project blocks proportional to historical share (capped at their historical average), then by fill blocks only for strong recurring patterns (≥3 of 4 weeks + cadence) for projects with no activity that day.consolidateByProjectService) — same project+service folds into one block (PRs preserved in the note); different services stay separate; meetings excluded.groupLinearIssuesIntoBlocks) — completed issues not referenced by a commit become their own classifiable blocks.mappingCacheKey) — strip the@HH:mmsession suffix so commit classification stops re-guessing daily.Calendar is highest priority
needsAction(attended but un-RSVP'd) now flow through; previously they vanished before becoming data.assignBlockColumnsgroups blocks into transitive overlap clusters and splits width only within a cluster, so a local overlap no longer narrows the whole day. 4px gutter + 3px vertical inset for breathing room.Leftover-blocks sidebar
Blocks the classifier found but the packer couldn't place no longer disappear:
PackDayUseCase.executeWithLeftoversreturns{ placed, leftovers }— overflowed real work ('overflow') + unused LLM suggestions ('suggestion', deduped by project+service). Meetings are never leftovers.LeftoverSidebar) auto-opens when leftovers exist, collapses to a rail with a count badge, and offers per-chip Toevoegen / Boek / Negeer plus a bulk Alles +.WeekPageroutes placed → timeline, unplaced → sidebar.Tests
1654 passing, typecheck + lint clean. New unit/component coverage for the trend engine, consolidation, grow-first + strong-pattern fill, leftover split, the cache key, Linear blocks, the RSVP filter, the overlap layout, and the sidebar.
Known caveats / easy toggles
🤖 Generated with Claude Code