An earlier page (18-where-time-lives, archived) compared three places the time axis could live and picked its own tab, arranged as a dated spine. This is that spine. Almost every record on it already ships — the phases, the two dates, the draws, the change orders, the inspections — filed across Budget, Plan and Maintenance where a binder reader will never look. The two things that do not ship are the two the paper binder is best at.
What 18 concluded, and this page was drawn to: the time axis gets a tab of its own, taking over the shipped Build destination, and that tab is a dated spine.
What superseded it. A blind comparison (23-which-navigation, archived) settled it, and the winner is drawn in this deck on Now and The house: three doors — Now · The house · Ask. There is no tab to take over. Nor is time a lens: 21 drew a fourth by when lens on its own frame 7 and cut it, because every one of its group headings turned out to be a sentence the other three lenses already print on a sub-line. The lenses are three — by room · by material · by who — and what time does in the architecture that stands is order the Now door.
Where this spine went. It is a phase view rather than a record view, which is why it did not fit the lens row and does not have to. It is now the sixth row of the binder, "The build so far", under The house, beside Money, Papers & permits, People, Care and Plans & drawings. That is FICTION.md's tab table, and it is 21's own wording: Plan becomes the binder's sixth row.
What that changed on this page. The name, which is now the pinned one and no longer the short form. And every annotation that argued this was a takeover of a shipped tab. No frame changed. No date, no arithmetic, no gap, no verdict about what is built — a spine, a two-column phase matrix at three widths, the delays named ahead and the five visit windows are the same drawings whichever door they are behind. 21 says as much in its own words: mockup 19's dated spine is not superseded at all.
The five phases and their order are canonical_phases with sort_order, served by GET /api/phases. Both phase titles here are real strings: Into the finishes is phaseFinishCloseout and Finishes & Close-Out is phaseLabelFinishCloseout, two registers of the same five in app_en.arb. The inspection is inspections, the draw is project_draws, and both have four live routes.
The screen. Not the records under it: the four rooms inside the open phase are derivable rather than stored — decisions.phase and decision_zones both ship, so the join needs no migration — but the dates beside each room are invented, because decisions.due_date is written only by a builder, by hand.
This annotation used to argue placement, and the argument is spent. It said the screen was missing but not the tab, because HcShell ships a fourth destination labeled Build (navBuildMobile, hc_scaffold.dart:165-166) going to /builder and BuilderScreen, and that the retired deck's mockup 18 closing note therefore had to argue a takeover. That shipped tab is still there and still goes to the builder-connection surface — what has gone is the reason to want it. The navigation is three doors, and this spine is the binder's sixth row, The build so far, under The house. Mockup 21 dissolves the Build destination on its own terms: the builder becomes a contact and a thread.
Rolling three closed phases into one line reads as an ordinary disclosure control and is not: it needs to know each phase closed, and a completed phase is exactly what nothing in the app records. Expanding it would need the same two dates frame 2 is asking for. The collapse is drawn because it is the right shape on a phone, not because it is the cheap half.
The draw and the inspection are drawn together because the lender releases on the inspection, and nothing in the schema joins them. project_draws carries a free-text category and no phase and no inspection id. inspections carries a phase. So the ledger and the schedule use two different vocabularies and cannot be asked the same question.
Node states, and what they are not. Filled is a phase that ended, the ring is where you are, hollow is ahead. There is no track that fills and no denominator anywhere on the frame — which is the correction, because the Plan screen's own timeline draws both.
For the whole house.
Just getting started
Structure is going up
Inside the walls
Into the finishes
The phase target column is stored only when someone supplies an actual project target. Track L does not create canonical phase-duration columns or seed a second phase catalog; it derives decision due dates from the latest actual phase, authored workstream duration, and lead-time inputs, then marks those dates with due_date_source='derived'.
The actual column was the sharpest gap on the page, and Track L closes it. PUT /projects/:p/phases/:ph writes project_phase_status.target_date, actual_date, and slip_cause, stamps updated_at for sync, updates projects.current_phase_id from the latest actual phase, and recomputes derived decision due dates in the same transaction.
There is still no separate due or planned column on project_phase_status. The stored phase fields are target, actual, and slip cause; decision-level due dates are materialized on decisions.due_date only when the system can derive them, and source-null explicit dates are preserved.
Which makes both columns of this frame new storage, not new UI — two dates on a phase, and a way for the person who knows to write them.
All of it, and there is nothing close. Grepping every migration for delay, risk and milestone finds one column: decision_library.delay_consequences, which is static library copy hanging off a decision and says what happens if you are slow to choose. That is a different thing from what usually goes long in a phase. There is no delays table, no risk register and no per-phase advisory of any kind.
A library table keyed on the five phase slugs, three to five rows each, seeded once and read on every project. Roughly the shape library_maintenance_tasks already has, which holds 53 hand-authored rows and is seeded from data/library/maintenance.json. So the pattern exists and only the content is missing.
Which is the argument for doing it early. This is content before it is engineering, and it is content nobody else writes because nobody else is willing to say out loud that the cabinet clock starts on the signature. The table is cheap; the fifteen sentences in it are the product.
One line on the frame is not library content and could not be: yours are drawn and priced, sign the change order. That reads this project's own project_change_orders row. A per-phase advisory is only worth the screen if the generic line can be followed by the specific one, and that is a join a library table cannot do on its own.
The next one worth the flight.
Cabinet set
Make it the pre-drywall walk.
It's the last day everything inside the wall is visible. A phone-full of photos taken that morning answers where is the stud, where is the wire for as long as you own the house, and no drawing answers it as well.
You took it, on 17 February. Thirty-four photos.
Thursday is a small one. The island wall is open for a day while Alvarez roughs it in, and then it closes for good. If you can't be there, ask for four photos before the drywall goes back on.
All of it, and nothing in the schema is named for it. No site_visits, no visit_windows, no visit_date — no table, column or index anywhere carries the word. Grepping the migrations for visit does return two hits, but both are prose in a comment (0022:7 revisit, 0072:10 on every visit), so the honest sentence is "no storage", not "no occurrences". There is also no calendar screen: the app has one date-picker widget (hc_date_picker.dart) called from three places across two screens, and no timeline-of-events surface at all.
The capture exists. The record does not — and an earlier version of this annotation had that backwards. A homeowner can take a progress photo without a message thread: SelfManagedCaptureCard offers note, voice and photo on the Build tab (builder_screen.dart:157, wired at :381, :443 and :459), and the last two are the no-builder and invite-pending states, which have no thread behind them at all. The zone workspace reaches camera capture too, and that path does attach the photo to a room. What is missing is narrower and still fatal to this frame: no gallery, no chronological view, no way to ask what is behind a given wall, and no FAB on the homeowner shell — so the thirty-four pre-drywall photos can be taken and never found again.
Which visit windows exist and roughly when they fall is derivable from the phase the project is in, the same way the delays are. What is not derivable is whether the homeowner took one, which is a row per project.
Thursday and frame 5 said two different things by wearing two different surfaces: a plain .card for a thing that happens, and .card-absent — the flat quiet surface mockup 16 introduced — for a thing the app cannot do. That convention ran across six pages and it is gone as of 2026-09-06, because .card-absent is a --surface-container fill at the card radius — the region’s ground wearing a card’s shape — and a region is a region of the screen, not a thing on it. Boxed at the card radius, an aside takes the same footprint as the options above it and reads as one nobody picked; the design package records itself making that exact correction to the callout, and its own absent tone is the outline rung, which is not for a sentence either. Both readings say the same thing. The meaning is not lost, because it was never in the box. Frame 5 still says it still has nowhere to write down how long in its own words, which is the sentence that was doing the work; the surface was only agreeing with it. What the app cannot do here is unchanged and still belongs in this annotation: nothing schedules the day, nothing asks for the four photos, and ask for four photos is what a homeowner does today because a message to the builder is the only path that works.
A change order can be filed as a schedule change. It still has nowhere to write down how long. CO-2 was the truss substitution, it cost nothing, and it moved this house three weeks. And the only number on that record is $0.
Every row. project_draws carries draw_number, description, category, status, draw_date, receipt_url, notes and amount, with four routes. project_change_orders carries title, change_type, status, requested_date and impact_amount, plus decision_id, state, co_number and a whole signature flow from migrations 0076 and 0078. inspections carries inspection_type, type_label, scheduled_at, phase, status, inspector_name and outcome_notes, with four routes.
The schedule magnitude, and it is the frame's whole argument. Be precise about which half is missing, because the coarse version of this claim is wrong: change_type really does accept schedule_change alongside cost_change and scope_change (change_orders.go:361), and the signature flow deliberately requires agreement on a $0 schedule_change because time is a cost. So a change order can be about the schedule. What it cannot do is measure it: the only impact columns are impact_amount and impact_amount_cents, both money, with no days column, no revised-date column and nothing that touches a phase. This pushes you a week is a sentence the record cannot hold, which leaves a $0 schedule change looking free. On the Budget screen that is invisible. On a time axis it is the point.
Draw 5 releasing on the June inspection is drawn and not stored. project_draws has a free-text category and no phase, no inspection id and no dependency of any kind. draw_date is text and amount is a float in dollars rather than cents. So the lender's milestones and the app's five phases are two vocabularies with nothing joining them.
Two smaller things, both still true. The dates on the draw spine cannot be entered. PaymentDraw.drawDate exists, budget_repository.dart sends draw_date when it is set, and the column is there — but _save() in draw_sheet.dart:143 submits amount, description, status, category and notes and no date at all, so nothing ever sets it. And change orders now carry two parallel status columns, the legacy status and the newer state from 0076, so a screen reading the wrong one will disagree with the signature flow.
Frame 6 — the matrix, on a wide screen — is not drawn at this width, because the whole point of it is that it only exists at one. Widen past 840 to see it. The same five phases and the same two date columns are immediately below as frame 7, which is the arrangement they take down here.
| Phase | Target | Actual | Ran long by |
|---|---|---|---|
| Just getting started | 1 Mar - 10 Aug 2026 | 1 Mar - 10 Aug 2026 | — |
| Structure is going up | 10 Aug - 30 Oct 2026 | 10 Aug - 20 Nov 2026 | 21 days |
| Inside the walls | 30 Oct 2026 - 19 Jan 2027 | 20 Nov 2026 - 9 Feb 2027 | — |
| Into the finishes | 19 Jan - 30 Jun 2027 | 9 Feb 2027 - running | — so far |
| Living & upkeep | Ongoing | — | — |
The wide chrome, all of it. Above a 520pt container HcNavMenu switches metric set on its own and keeps its persistent utilities in the header — which here is the avatar alone, because this product has no inbox and so no dock in the deck carries a bell. Mockup 15 measures the open panel at 356pt. Drawn collapsed here, because the matrix is what this frame is about.
The Actual column, exactly as on frame 2. Widening the screen does not change what the schema holds; it only makes the empty column obvious, because on a desktop table a blank column is a blank column.
The fourth column is the one thing here the phone cannot carry, and what it holds decides what the table says. It is Ran long by — each phase's own overrun — not the calendar's distance from target, which reads 21, 21, 21 down three rows and looks like three delays. Read this column down and there is a single number in it. That is the whole finding, and it only becomes a glance in a table; on frame 2 it takes a sentence per card and a summary under them.
Just getting started
Structure is going up
Inside the walls
Into the finishes
Living & upkeep has no date arithmetic and that is correct. If a decision has no actual phase anchor, no primary workstream, no workstream duration, or no lead-time input, Track L leaves the derived date empty. A table has to draw that as a blank row; a stack can say why.
Because a four-column table at 600 is four columns at 150 each, and two of those columns hold a date range like 30 Oct 2026 - 19 Jan 2027. Narrowing produces four wrapped columns of ragged type and a header row nobody can align to. The arrangement has to change, not the scale, and the switch has to be drawn to be believed.
The sentence under the run is the case that only shows up when you draw both. In a table, Living & upkeep is a row with three em-dashes in it, which looks like missing data. Under a stack it can be a sentence explaining that the blank is correct — the migration really does leave both duration columns NULL for that phase on purpose. Same data, and only one of the two arrangements can be honest about it without a footnote.
21 frame 10 in green.If a builder joins this house, this list becomes theirs to keep and yours to read. Nothing you have written down goes away.
41's axis is where a homeowner meets a visit her builder booked: she reads it, and the useful question is whether it is worth standing in the house for. A person who is her own GC is not reading somebody's schedule, she is keeping one, and the useful question is who has not called back. Drawing 21 frame 10 in emerald would have answered the first question with the second screen.
The builder reaches this from a job, one visit at a time, and lands on a form. She has no job screen and no dispatcher, so what she needs on opening is the whole week in one card. The write is a single cta below it, and the fields behind it are 21 frame 10's, unchanged: the same table, the same free-text trade, the same calendar day.
Q145's condition is while the project has no connected builder, so this is a capability that can end without her doing anything. The callout is the whole of what the product says about that, in advance and once. A screen that let her keep writing and then refused at the boundary would be the same fact delivered as a failure.
X3 and X4 served both writers and 21 drew one of them. A tick covering two routes and one of their two writers is how the other half stops being counted, so the row went into OWED_WORK.md the same afternoon and this frame is what closes it.
Your build timeline starts when construction does. Until then this page holds who is coming to the site.
The pre-build copy is a deliberate refusal to draw a dated spine before there is one, and a phase band here would contradict it. So the callout survives word for word and the visits go under it, in the two bands they already have on frame 1. What changes is only that the page stops claiming there is nothing when there is.
A survey and a soils boring are exactly the visits that happen before a phase exists, and both are decided against: the geotech reads next to the foundation type. A homeowner who searches for geotech lands here, and the version of this page without this section is the one that told her it had not started yet.
That review verified the banding and did not verify the state it bands in. readTimelineTradeVisits runs whatever the state and the projection carries them the whole time; only the screen dropped them. It is worth saying plainly, because a ruling checked against the state it was written for is the shape of gap that survives its own review.
The audit's finding was that the time material is already built and shipped and filed where a binder reader will never look. That is mostly right, and this page is the mostly. Four of the paper binder's twenty-three capabilities land here as routing and layout rather than as new capability — the whole-build start and target dates, the key dates (which in this app are the inspections), the draw schedule and the construction milestones. Four do not, and the count matters because an earlier draft of this paragraph put the phase tracker in the first list while three other places on this page correctly said it was unbuilt.
GET /api/phases serves the phase catalog; PUT /projects/:p/phases/:ph writes the project phase row's target, actual, and slip cause. The actual date also drives projects.current_phase_id and derived decision due dates, while sync carries project_phase_status changes by updated timestamp.change_type accepts schedule_change, so the record can be about time; there is no days column and no revised-date column, so it cannot say how much. On a time axis that is the sharpest single omission on the page.library_maintenance_tasks, which already holds 53 hand-authored rows seeded from a JSON file. The table is a day. The sentences are the product, and nobody else writes them.project_phase_status. A schedule-magnitude column on project_change_orders. And a zone on one thing, so a room tab can carry the one row the retired deck's mockup 18 kept from arrangement B.HcShell ships four destinations and the fourth is already labeled Build, routing to BuilderScreen — both still true in the code today. The retired deck's mockup 18 made the call to take it over and priced it. The navigation question has since been settled the other way: three doors, and this spine is the binder's sixth row under The house. Nothing in the list above changes for it. Every item is a statement about what the schema holds, and a schema does not know which door a screen is behind. That is the distinction worth keeping in view — a superseded placement does not supersede a design, and every drawing on this page is the design.Naming a delay before it happens converts a shock into a briefing. A homeowner who has read cabinets are made to order, six to ten weeks, and the clock starts on your signature in April is a different person in June from one who has not. The information is free, it is true on almost every build, and it is the difference between a product that reports and a product that coaches. It also does the thing the whole set is trying to do: it lowers anxiety by being specific rather than by being reassuring.
Telling a remote homeowner which moment is worth a flight is the highest-stakes logistics call in the build, and nobody makes it for them. Five moments justify standing in the house and exactly one justifies rearranging your life around it. Getting that one right is worth more to a homeowner three time zones away than any other single sentence in the app, and the answer is not a preference — it is pre-drywall, every time, because it is the only day the inside of the wall is a photograph rather than a memory.
The phase matrix is drawn three times because it is the one shape in this set that cannot be scaled. As a table it wants four columns; as a card it wants two. Frame 6 is the table at a 1120pt container, frame 7 is the stack at 600, and frame 2 is the same stack at 390. Between 6 and 7 the arrangement changes: Phase becomes a title, Target and Actual become two headed columns inside the card, and the fourth column becomes a sentence. No column is dropped and nothing is shrunk to fit.
And the switch is now enforced, which it was not. This page asserted that the matrix is never rendered below 840 and there was no media query anywhere in the set to make it so. At 600 the four columns resolved to 60 / 76 / 74 / 102px, the table overflowed its card by 68px and the canvas clipped the rest — the exact failure the paragraph above says must not happen, happening two screens below the paragraph. Frame 6 is now gated at min-width: 840px and a line takes its place under it. An adaptive claim that is only a sentence is a responsive layout with a good opinion of itself.
Drawing both surfaced something a media query would have hidden. Some rows have no derived date because a real timing input is absent: no actual phase anchor, no primary workstream, no workstream duration, or no lead-time input. In the table that is a row of em-dashes, which reads as missing data. In the stack it can be a sentence saying the blank is correct. One arrangement can be honest about it and the other needs a footnote, and that is the kind of thing you only find by drawing the second one.
No progress bar and no ratio on a homeowner surface. The Plan screen currently draws both: _TimelineBar at plan_screen.dart:800 is a segmented emerald bar, one pill per phase, and plan_screen.dart:515 renders the string "Phase {current} of {total}". Neither appears anywhere on this page.
And the bar is not only against the rule, it is not evidence. PlanService.segmentsFor fills a segment purely from its position relative to the current phase — everything before is drawn complete. Since nothing outside tests ever writes a project_phase_status row, no completion is ever recorded, so the bar reports an index dressed as a measurement. A spine node states a date and a state and claims nothing else, which is both calmer and more truthful.
_ds/FICTION.md now pins actuals where the truss phase runs 21 days long and every other phase runs exactly its window, and the pages take their dates from there.inspections.inspector_name is a real column and every row here reads County building inspector. That is pinned in FICTION.md rather than left blank, because a blank cell in that file is what produced two different hex values for the same paint on the same night.decisions.phase and decision_zones both ship, so it is a query. The draw is not: project_draws has a free-text category and no phase and no inspection id. Both annotations say which is which, because a drawing that quietly implies a join is the thing this set exists to stop._ds/hc.css, because the retired deck's mockup 18 draws it too — and its node-column selectors are child combinators there for a reason: written as descendants they also matched the .n title of every stacked row inside an opened node, won on source order, and shoved those titles up to 68px right of their own sub-lines on both pages.Cited as the thing not to do: searching for a "limited window" pattern returns almost nothing but countdown timers — FocusFlight, Pillow and Peanut all put a ticking clock on a closing window, and Deel's onboarding puts a red completion bar and "2/5 steps completed" on a schedule. A pre-drywall walkthrough is a genuinely closing window, which is exactly why it must not be drawn like a discount expiring. Sunlitt's golden hour is the same information with the urgency taken out, and that is the version this product ships.