fix(store): give the layanan body the section padding every other page has #87

Merged
yuris merged 2 commits from fix/services-spacing into main 2026-09-05 08:57:25 +00:00
Owner

Reported: the /services body spacing does not match the other pages. Measured, and the report is exact.

What was wrong

Header bottom to first painted ink, all seven routes, both widths:

route 1440 375
/ 32 24 hero band, own rhythm
/products 16 16 breadcrumb page
/products/[slug] 16 16 breadcrumb page
/services/[slug] 16 16 breadcrumb page
/cart 16 16 breadcrumb page
/contact 44 28 landing page, .section
/services 0 0 flush against the header

Every route matched its mockup exactly beforehand, so this was a defect in the approved composition that the port carried over faithfully — not port drift.

Root cause

One missing element. kontak.html opens with <section class="section" style="padding-bottom:var(--s6)"> and follows it with a padding-top:0 section: the pair reads 44/24 then 0/44 and abuts without doubling.

layanan.html had the identical padding-top:0 second section, but its first block was a bare <div class="container"> leaning on an inner margin-bottom. With nothing above paying the top padding, the padding-top:0 that exists to prevent doubling instead collapsed the body against the header — and that 24px margin made layanan the only page whose blocks are separated by a margin rather than by section padding. A double anomaly from a single omission.

Fix

At the source, not papered over in the app: the intro in the OpenDesign project is now a .section with padding-bottom:var(--s6), the redundant inner margin-bottom is gone, re-vendored to apps/store/design, and mirrored in app/services/page.tsx.

/services now measures 44/28 with section:44px/24px then section:0px/44px — byte-for-byte /contact's shape, the right sibling since both are landing pages with no breadcrumb.

Checked, not assumed

/ opens tighter at 32/24 because .hero is a distinct band with its own padding — correct by design.

/products/[slug]’s 96px bottom padding at 375 is not correct by design, and an earlier version of this PR wrongly said it was. Measured, it is wrong twice: the .sticky-buy bar is 69px so 96px over-reserves by 27px, and because the bar is position: fixed it overlays the viewport bottom rather than the end of .pdp (which at max scroll sits at viewport y -11, off-screen). body padding-bottom is 0, so what is actually under the bar is the footer — and two footer links, Kaki-kaki and Cara pesan, resolve to the bar’s own price span and CTA button at their own centres. They are untappable.

Pre-existing on main, reproduces in the approved mockup, and .pdp{padding-bottom} is not in this diff — so it is out of scope here and filed as #89.

Extra coverage added during review

The tablet band was unmeasured in the first version of this PR. Now covered: /services and /contact are identical at 1440/1121/1120/900/701/700/375, and the breakpoint boundaries are clean (1121→44, 1120→36, 701→36, 700→28) with no off-by-one.

A naive "first text ink" metric reads /services 45 vs /contact 44. That 1px is line-box half-leading, not spacing: /services opens on p.eyebrow (12px font in an 18px line box), /contact on h1 (28px in 32.2px). Both sections’ content-box top is 105px. Not a divergence.

Verification

  • Real browser at 1440 and 375, all seven routes: delta 0 against the mockups
  • layanan.html has no <style> block, so pnpm port-design emits a byte-identical globals.css
  • All seven vendored design files still byte-identical to the OpenDesign project
  • 504 tests, both typechecks exit 0, gate PASSED
Reported: the `/services` body spacing does not match the other pages. Measured, and the report is exact. ## What was wrong Header bottom to first painted ink, all seven routes, both widths: | route | 1440 | 375 | | |---|---|---|---| | `/` | 32 | 24 | hero band, own rhythm | | `/products` | 16 | 16 | breadcrumb page | | `/products/[slug]` | 16 | 16 | breadcrumb page | | `/services/[slug]` | 16 | 16 | breadcrumb page | | `/cart` | 16 | 16 | breadcrumb page | | `/contact` | 44 | 28 | landing page, `.section` | | **`/services`** | **0** | **0** | flush against the header | Every route matched its mockup exactly beforehand, so this was a defect in the approved composition that the port carried over faithfully — not port drift. ## Root cause One missing element. `kontak.html` opens with `<section class="section" style="padding-bottom:var(--s6)">` and follows it with a `padding-top:0` section: the pair reads 44/24 then 0/44 and abuts without doubling. `layanan.html` had the **identical** `padding-top:0` second section, but its first block was a bare `<div class="container">` leaning on an inner `margin-bottom`. With nothing above paying the top padding, the `padding-top:0` that exists to prevent doubling instead collapsed the body against the header — and that 24px margin made layanan the only page whose blocks are separated by a margin rather than by section padding. A double anomaly from a single omission. ## Fix At the source, not papered over in the app: the intro in the OpenDesign project is now a `.section` with `padding-bottom:var(--s6)`, the redundant inner `margin-bottom` is gone, re-vendored to `apps/store/design`, and mirrored in `app/services/page.tsx`. `/services` now measures **44/28** with `section:44px/24px` then `section:0px/44px` — byte-for-byte `/contact`'s shape, the right sibling since both are landing pages with no breadcrumb. ## Checked, not assumed `/` opens tighter at 32/24 because `.hero` is a distinct band with its own padding — correct by design. `/products/[slug]`’s 96px bottom padding at 375 is **not** correct by design, and an earlier version of this PR wrongly said it was. Measured, it is wrong twice: the `.sticky-buy` bar is 69px so 96px over-reserves by 27px, and because the bar is `position: fixed` it overlays the viewport bottom rather than the end of `.pdp` (which at max scroll sits at viewport y -11, off-screen). `body` padding-bottom is 0, so what is actually under the bar is the footer — and two footer links, `Kaki-kaki` and `Cara pesan`, resolve to the bar’s own price span and CTA button at their own centres. They are untappable. Pre-existing on `main`, reproduces in the approved mockup, and `.pdp{padding-bottom}` is not in this diff — so it is out of scope here and filed as **#89**. ## Extra coverage added during review The tablet band was unmeasured in the first version of this PR. Now covered: `/services` and `/contact` are identical at 1440/1121/1120/900/701/700/375, and the breakpoint boundaries are clean (1121→44, 1120→36, 701→36, 700→28) with no off-by-one. A naive "first text ink" metric reads `/services` 45 vs `/contact` 44. That 1px is line-box half-leading, not spacing: `/services` opens on `p.eyebrow` (12px font in an 18px line box), `/contact` on `h1` (28px in 32.2px). Both sections’ content-box top is 105px. Not a divergence. ## Verification - Real browser at 1440 and 375, all seven routes: **delta 0** against the mockups - `layanan.html` has no `<style>` block, so `pnpm port-design` emits a **byte-identical** `globals.css` - All seven vendored design files still byte-identical to the OpenDesign project - 504 tests, both typechecks exit 0, gate PASSED
fix(store): give the layanan body the section padding every other page has
All checks were successful
Checks / test @garage/catalog (pull_request) Successful in 1m21s
Checks / test garage (pull_request) Successful in 1m22s
Checks / test garage-store (pull_request) Successful in 1m19s
Checks / typecheck apps/management (pull_request) Successful in 1m34s
Checks / lint (biome) (pull_request) Successful in 12s
Checks / typecheck apps/store (pull_request) Successful in 1m22s
67979330e3
Reported: the /services body spacing does not match the other pages. Measured
it across all seven routes at 1440 and 375 — header bottom to first painted
ink — and the report is exactly right:

  route                  1440   375
  /                        32    24   hero band, its own rhythm
  /products                16    16   breadcrumb page
  /products/[slug]         16    16   breadcrumb page
  /services/[slug]         16    16   breadcrumb page
  /cart                    16    16   breadcrumb page
  /contact                 44    28   landing page, .section
  /services                 0     0   <-- flush against the header

Every route matched its mockup exactly before this change, so the defect was
in the approved composition and the port had carried it over faithfully.

Root cause, one missing element. `kontak.html` opens with
`<section class="section" style="padding-bottom:var(--s6)">` and follows it
with a `padding-top:0` section, so the pair reads 44/24 then 0/44 and the
blocks abut without doubling. `layanan.html` has the identical
`padding-top:0` second section, but its first block was a bare
`<div class="container">` leaning on an inner `margin-bottom`. With nothing
above paying the top padding, the `padding-top:0` that exists to avoid
doubling instead collapsed the whole body against the header — and the
24px margin left layanan the only page whose blocks are separated by a
margin rather than by section padding.

Fixed at the source, not papered over in the app: the intro in the
OpenDesign project is now a `.section` with `padding-bottom:var(--s6)` and
the inner div's redundant `margin-bottom` is gone, then re-vendored to
apps/store/design and mirrored in app/services/page.tsx. /services now
measures 44/28 to first ink with `section:44px/24px` then `section:0px/44px`
— byte-for-byte /contact's shape, which is the right sibling since both are
landing pages with no breadcrumb.

Two values that look odd and are not bugs, checked rather than assumed:
`/` opens at 32/24 because `.hero` is a distinct band with its own padding,
and `/products/[slug]` has 96px bottom padding at 375 because `.sticky-buy`
becomes `position: fixed; inset: auto 0 0 0` there and the content needs
clearance from it.

The stylesheet is untouched: layanan.html carries no `<style>` block, so
`pnpm port-design` still emits a byte-identical globals.css. All seven
vendored design files remain byte-identical to the OpenDesign project.

504 tests, both typechecks exit 0, gate PASSED, verified in a real browser at
1440 and 375 against both the app and the corrected mockup (delta 0 on every
route at both widths).
yuris force-pushed fix/services-spacing from 67979330e3
All checks were successful
Checks / test @garage/catalog (pull_request) Successful in 1m21s
Checks / test garage (pull_request) Successful in 1m22s
Checks / test garage-store (pull_request) Successful in 1m19s
Checks / typecheck apps/management (pull_request) Successful in 1m34s
Checks / lint (biome) (pull_request) Successful in 12s
Checks / typecheck apps/store (pull_request) Successful in 1m22s
to 8616bd3bdd
All checks were successful
Checks / test @garage/catalog (pull_request) Successful in 1m13s
Checks / test garage (pull_request) Successful in 1m22s
Checks / test garage-store (pull_request) Successful in 1m16s
Checks / typecheck apps/management (pull_request) Successful in 1m42s
Checks / lint (biome) (pull_request) Successful in 12s
Checks / typecheck apps/store (pull_request) Successful in 1m19s
2026-09-05 08:07:46 +00:00
Compare
docs(store): record the one-way trapdoor the hand-edit left in design refresh
All checks were successful
Checks / test @garage/catalog (pull_request) Successful in 1m16s
Checks / test garage (pull_request) Successful in 1m19s
Checks / test garage-store (pull_request) Successful in 1m23s
Checks / typecheck apps/management (pull_request) Successful in 1m33s
Checks / lint (biome) (pull_request) Successful in 11s
Checks / typecheck apps/store (pull_request) Successful in 1m25s
c20f9692ae
Review of #87 by SourceOfTruth: the hand-edit to the approved composition was
the right call (OpenDesign's runtime was unavailable, and routing through it
would have produced the same six-line wrapper with a fabricated prompt
attached), but it needs a marker — and the marker does NOT belong in the
OpenDesign project.

The drift is real and measurable. OD's version ledger for layanan.html
(.file-versions/eb1df65b…/manifest.json, schemaVersion 2, 4 entries) still
lists v4 fcb0bba9 / 8010 bytes as currentVersionId. That stored blob is
byte-identical to the pre-fix file — verified with cmp against
`git show main:apps/store/design/layanan.html`. The true current file,
5bf0f305 / 8076 bytes, matches no version OD has stored.

So OpenDesign believes the approved artifact is the one with the bug, and a
refresh that restores layanan.html from its version history silently
reintroduces the defect. My documented refresh procedure — "refresh design/
from the OpenDesign project, then re-run" — had a trapdoor in exactly one
direction and did not say so. That, not the unrecorded edit, was the hazard.

Recorded in port-design.mjs's existing vendoring comment rather than under
~/.od, for the reasons the reviewer gave: manifest.json is content-addressed
with uuid ids and prompt/promptSource fields, so hand-forging a v5 means
inventing every field and is worse than an absent entry if the daemon returns
and reconciles; artifact.json carries metadata OD sets itself; and anything
under ~/.od is outside git, unreviewable in a PR, and gone if the project is
regenerated — the exact failure the marker exists to prevent. The vendoring
comment is already the authoritative statement of this relationship and is
true on a fresh clone.

The note says the next refresh must diff per file and adjudicate rather than
overwrite wholesale, and that every other file is genuinely identical so cmp
still tells the truth about them.

It also records how the bug got there, because the shape recurs: OD v4's
prompt was "No need breadcrumbs in the layanan page." v1-v3 opened with
`<nav class="crumbs">`, which carries 16px padding-block and was the only
element paying the body's top padding. Deleting it left nothing in its place —
a targeted removal that did not re-establish what the removed element was
incidentally providing.

Comment-only: globals.css re-emits byte-identical, port still idempotent.
yuris force-pushed fix/services-spacing from c20f9692ae
All checks were successful
Checks / test @garage/catalog (pull_request) Successful in 1m16s
Checks / test garage (pull_request) Successful in 1m19s
Checks / test garage-store (pull_request) Successful in 1m23s
Checks / typecheck apps/management (pull_request) Successful in 1m33s
Checks / lint (biome) (pull_request) Successful in 11s
Checks / typecheck apps/store (pull_request) Successful in 1m25s
to acec9134cc
All checks were successful
Checks / test @garage/catalog (pull_request) Successful in 1m16s
Checks / test garage (pull_request) Successful in 1m20s
Checks / test garage-store (pull_request) Successful in 1m21s
Checks / typecheck apps/management (pull_request) Successful in 1m37s
Checks / lint (biome) (pull_request) Successful in 12s
Checks / typecheck apps/store (pull_request) Successful in 1m24s
2026-09-05 08:51:00 +00:00
Compare
yuris merged commit 59292ef660 into main 2026-09-05 08:57:25 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/garage!87
No description provided.