feat(design): Servizio — the app asks, you answer, it hands you a plan - #298
feat(design): Servizio — the app asks, you answer, it hands you a plan#298JanWelker wants to merge 1 commit into
Conversation
Knead Time stops being a form. It opens with one question set large — when are you eating? — and walks through four more, each answerable in a single gesture, with the plan forming beside it. The answer is a place you return to: a full-screen schedule where every value is underlined and opens for editing where it stands. The layout is redrawn rather than restyled. There is no left form column, no right schedule column and no stack of collapsed sections at the foot of the page; there are three views, exactly one of which is mounted at a time, and src/lib/view.ts owns which. The place lives in the URL fragment so it is linkable, survives a reload and walks with the back button — the recipe query is untouched, no key was added and CURRENT_VERSION stays 6, so every published share-link still resolves to the same recipe. Nobody is walked through the questions twice: anyone arriving with a share link or the recipe this device last worked on lands straight on the plan. Anyone who already knows all twelve numbers opens the Adjust sheet — a native modal dialog holding every DoughInputs field on one dense surface — and fills them in without stepping through anything. Visually: no cards, one grotesque separated by size and weight instead of a serif/sans pair, a limewashed-plaster ground in place of cream, and the dough colour spent only where something is actually fermenting. Tomato now means acting or danger and nothing else. The browser suite reaches the same rules through the new structure and gains eight views/flow specs plus axe coverage of the sheet, the questions and the library; the unit suite gains view.ts at 100 %.
|
Direct feedback on the old design was that it looked like it was made by an engineer, and it did: every region was the same cream card, the page opened with twelve near-identical label-over-input rows, and the schedule — the thing the app is for — sat below the fold on a phone and no louder than the fridge temperature field. This is the redesign that answers it, combining the structure of PR #298 with the visual language of PR #297. THE STRUCTURE. The app is three places, and which one is on screen lives in the URL fragment (`#ask/<step>`, `#plan`, `#library`) so it is linkable, survives a reload and walks with the back button — and adds no query key, so the recipe schema is untouched and `hasRecipeParams` needs no exception. It opens with one question at 72 px, then four more, each answerable in a gesture and each skippable. Anyone arriving with a share link or a remembered recipe lands straight on the plan and is never asked anything. The plan carries no inputs: every value is a blank on a printed form that opens the adjust sheet focused on that field, and the sheet is the expert's door — one press to twenty numbers. The collections became a view of their own, since they are entry points to a recipe rather than an appendix to one. THE PRESS. Ink does the work shadows used to: flat paper stock, 2 px rules, square corners, offset blocks, nothing blurred. Anton for the sign, Archivo's width axis for the condensed caps, loaded as a preload that promotes itself so a slow font server can never block first paint. The schedule is a numbered job ticket where the line number IS the rail node; the ingredients are a deli ticket with dotted leaders and a real punched perforation; the masthead is a painted enamel plaque over a tricolore hairline that closes the colophon too — that flag is what holds three views together as one publication. Every control sits with the thing it acts on: the buttons under the flag with the values they change, the mode and fit seals on the schedule's lede line, each opening what its mark means. Language, theme and step detail are choices in one menu, which is why SegmentedControl and the pill strip are gone. The mode and the fit are drawn guild seals rather than outlined boxes carrying ASCII stars. The maths is untouched. `src/lib/dough/**`, the .ics builder, the TRMNL payload and the print route are unchanged; every v=1…v=6 link reproduces its recipe exactly, and `CURRENT_VERSION` stays 6, so this is a minor bump to 6.11.0. Testing: 951 unit at 100 % coverage, 122 browser tests (from 940/90). Nothing was weakened to fit the new structure — specs were rewritten to reach the same rules through it. Bugs found and fixed along the way, each with its own test: a question's slide-in painting past the sheet and putting a scrollbar under every move at 390 px; the tricolore's middle panel vanishing on the dark press, because the flag's white is the stock; a dotted leader stranded as a stub beside a flour name that wrapped; a fit panel that lost its position, border, fill and shadow at once because `@apply` reached for another components class; and a menu that painted behind the schedule card after a transform trapped its stacking context.
BREAKING CHANGE: the default experience is a different app. Anyone returning to a bookmark meets a question flow and a job ticket where a form and a table used to be, which is what CLAUDE.md calls a user contract break, so this is 7.0.0. Direct feedback on the old design was that it looked like it was made by an engineer, and it did: every region was the same cream card, the page opened with twelve near-identical label-over-input rows, and the schedule — the thing the app is for — sat below the fold on a phone and no louder than the fridge temperature field. This is the redesign that answers it, combining the structure of PR #298 with the visual language of PR #297. THE STRUCTURE. The app is three places, and which one is on screen lives in the URL fragment (`#ask/<step>`, `#plan`, `#library`) so it is linkable, survives a reload and walks with the back button — and adds no query key, so the recipe schema is untouched and `hasRecipeParams` needs no exception. It opens with one question at 72 px, then four more, each answerable in a gesture and each skippable. Anyone arriving with a share link or a remembered recipe lands straight on the plan and is never asked anything. The plan carries no inputs: every value is a blank on a printed form that opens the adjust sheet focused on that field, and the sheet is the expert's door — one press to twenty numbers. The collections became a view of their own, since they are entry points to a recipe rather than an appendix to one. THE PRESS. Ink does the work shadows used to: flat paper stock, 2 px rules, square corners, offset blocks, nothing blurred. Anton for the sign, Archivo's width axis for the condensed caps, loaded as a preload that promotes itself so a slow font server can never block first paint. The schedule is a numbered job ticket where the line number IS the rail node; the ingredients are a deli ticket with dotted leaders and a real punched perforation; the masthead is a painted enamel plaque over a tricolore hairline that closes the colophon too — that flag is what holds three views together as one publication. Every control sits with the thing it acts on: the buttons under the flag with the values they change, the mode and fit seals on the schedule's lede line, each opening what its mark means. Language, theme and step detail are choices in one menu, which is why SegmentedControl and the pill strip are gone. The mode and the fit are drawn guild seals rather than outlined boxes carrying ASCII stars. THE SCHEMA IS NOT BROKEN. `src/lib/dough/**`, the .ics builder, the TRMNL payload and the print route are unchanged, and every v=1…v=6 link reproduces its recipe exactly. CURRENT_VERSION goes to 7 only because the major version is pinned to it: v=7 adds no key and no gate, both existing gates sit below it, and two tests hold that — the encoded key set is unchanged, and the same query decodes identically at v=6 and v=7. Testing: 953 unit at 100 % coverage, 122 browser (from 940/90). Nothing was weakened to fit the new structure — specs were rewritten to reach the same rules through it. Bugs found and fixed along the way, each with its own test: a question's slide-in painting past the sheet and putting a scrollbar under every move at 390 px; the tricolore's middle panel vanishing on the dark press, because the flag's white is the stock; a dotted leader stranded as a stub beside a flour name that wrapped; a fit panel that lost its position, border, fill and shadow at once because `@apply` reached for another components class; and a menu that painted behind the schedule card after a transform trapped its stacking context.
|
Closing: this one won the structure. #300 merged as v7.0.0 and carries this branch's architecture intact — the three views with the place in the URL fragment ( Reading the view state out of the fragment rather than the query is the decision that made the whole thing possible: no new query key, so the recipe schema was untouched and Superseded by #300 — not abandoned, merged. |
The app stops being a form and becomes a service: it asks a short, confident sequence of questions and hands back a plan you live inside for two days.
This is one of the two "rethink it completely" directions, so the layout, the navigation and the order in which the app asks and answers are all redrawn. There is no left form column, no right schedule column and no stack of collapsed sections at the foot of the page.
What a user sees differently
The landing is one question, not twelve inputs
The first screen asks when are you eating? at 72 px with the answer already sensibly filled in. Everything in this app schedules backwards from that moment and it had never once been staged that way.
Then four more real decisions, one per screen, full viewport: how many pizzas, which flour is in your cupboard, how long it ferments (the slider gets a whole screen — it is the decision that changes everything), and how you knead it. Each is answerable in one gesture. Each is skippable: "Skip to the plan" is on question one, and the progress rail jumps to any of the five. A live glance beside the question shows the plan forming — start moment, window, flour, mode, step count — so it never reads as a survey.
The plan is a place you return to
The destination is the schedule as a standalone view: the thing you open at 07:00 the next morning with flour on your hands, so it is the calm one. The bake moment is the largest thing on the page. Nothing on it is an input — every editable value is an underlined chip.
Editing happens against the live plan
Tap the bake time, the pizza count, the hydration — the Adjust sheet opens with that field focused. It is a right-hand drawer above
lg(the schedule stays visible and live behind it) and a bottom sheet below, and it holds every field inDoughInputson one dense surface. That is also the expert's door: one press to twenty numbers, no wizard in the way.The collections became entry points
My recipes, Community and 50 Top Pizza are a Recipes view of their own, one press from the first question and one from the plan, instead of three disclosures at the bottom of a 5600 px column.
What moved where
Nothing was dropped. For each region the brief lists:
AdjustPanel— a modal<dialog>opened from the plan's Adjust button or from any chip.InputForm.sveltestill renders them, groupedWhen/Batch/Dough/ what leavens it / where it proofs. Beginner/expert still decide which appear.AskFlow.svelte), and shown as editable chips on the plan.ScheduleTable.svelte), rebuilt as a three-column timeline — time in a hanging gutter, rail, step.xl, under the schedule below it. "Round numbers" sits with it.window+temperatureunder the plan's summary chips (above the schedule);ingredientsbeside the weights;windowagain inside the slider on the ask flow (opt-in via a newwarningsprop, so it is never rendered twice).warningSlots.tsis unchanged.infoSections.tsuntouched.LibraryView(#library), still collapsed disclosures, still one.mdeach..ics, print, save, TRMNL)SiteFooter.svelte, under every view./print/[[locale]]Navigation
src/lib/view.ts(pure, 100 % covered) is the whole of it:AppView = 'ask' | 'plan' | 'library', five ask steps, andinitialLocation().#ask/<step>,#plan,#library— and adds no query key. The recipe query is byte-for-byte what it always was,hasRecipeParamsneeds no exception list, and the fragment is linkable, survives a reload and walks with the back button.initialLocation()never walks a returning baker through the questions. An explicit fragment wins; then anyone arriving with a recipe — a share link, a saved recipe, or the one this device last worked on — goes straight to the plan. A truly fresh visit gets question one.Design tokens and component classes
Palette. Five surface roles as
--kt-*on:root/.dark, re-exported through@themeas--color-{ground,plane,ink,ink-soft,line,line-soft}. The ground is limewashed plaster with a green cast (#e9ece1/#141711), not cookbook cream. Dough is spent only where something is fermenting; tomato means acting or danger and nothing else — it came off section headings, totals and the room-ferment badge. The three hue ramps are unchanged and--color-tomato-500is still#c8401a(three specs pin the focus ring to it).Type. One grotesque, three roles separated by size, weight and tracking rather than a serif/sans pair. No webfont — this is a static build that makes no third-party requests and a Google Fonts link would be its only one.
Structure.
.cardis gone. Regions are separated by space and one.rulehairline;.plane(the only radius + shadow) is for things that genuinely float, so radius reads as elevation.New in
@layer components:.view,.view-pad,.rule,.plane,.question,.lede,.data,.field-label,.section-head,.text-time,.chip,.btn-ghost,.tile,.stepper,.dot/.dot-on,.wordmark,.input-lg,.window-card. Removed:.card.Motion. One orchestrated moment: moving between questions slides the question block as a single surface (
.kt-enter, direction-aware). It is the only non-user-triggered animation besides the current step's node pulse, both silent underprefers-reduced-motion— pinned by a test.Testing
e2e/encodes rules, not layout, but it reached them through the old layout, so the specs were rewritten to test the same rules through the new structure. Nothing was weakened, deleted or lowered:helpers.tsnow addresses the app throughmain [data-view],region()(a section addressed by heading),sheet()/openAdjust(),.window-card,#field-window.card()andformCard()went with.card.save-recipechecks the book in the library.warnings.spec.tsasserts the same slot mapping against the new mount points, and gained a check that the window family follows the slider onto the ask flow.view.ts), 90 → 114 browser.e2e/views.spec.ts(8 tests — landing rules, reload, back/forward, the recipe query surviving every move, chip-to-focus, sheet dismissal) ande2e/ask-flow.spec.ts(5 — one gesture per question, skippability, the stepper, the shared slider, reduced motion).axe.spec.tsgrew from 4 runs to 12: plan, sheet, questions and library, each in both themes and (where it matters) both view modes.Checks run, honestly:
npm run lintnpm run checknpm run test:coveragenpm run test:baselineE2E_PORT=4185 npm run test:e2eplaywright.config.tstakesE2E_PORT(Number(process.env.E2E_PORT ?? 4173)) so four agents can share this machine — all four sibling PRs make this identical one-line change.Versioning —
npm version minor→ 6.11.0,CURRENT_VERSIONstays 6Judged against the addendum's test: the contract did not move. No URL key was added, renamed or repurposed; the fragment carries the view precisely so the query would not have to. Every published
v=1 … v=6link decodes identically and reproduces its recipe gram for gram and minute for minute —urlState.test.tsand the legacy-link e2e specs are unchanged and green. EveryDoughInputsfield is still reachable and editable by mouse, touch and keyboard; beginner and expert both still exist and still mean what they mean.And a returning bookmark-holder is not made to answer anything: they land on their plan. What changed is the arrangement and the aesthetics, and the addendum is explicit that those alone are not a major bump.
This bumps to the same 6.11.0 as the sibling redesign PRs and will conflict with them on
package.json/package-lock.jsonat merge. That is expected — resolve by keeping one 6.11.0..github/test-baseline.jsonwill also conflict with any sibling that added tests; take the higher number of each.Deliberately left undone