ReviewOS

stacks/smakelo

B6: Launch gate - demo guards, i18n QA, design pre-flight, deploy

#19
Open chris opened this 9 days ago ·
9 days ago

Part of #1.

  • Demo guards verified: noindex + robots, dismissible demo banner, sandbox badge at checkout, zero outbound email/SMS to real businesses (notification allowlist)
  • i18n QA: en/de/nl complete across all surfaces; EUR market fixture behaves (VAT-inclusive display, iDEAL/SEPA test methods)
  • Design pre-flight per stacks-design-taste section 14 on every surface
  • E2E QA via browser: delivery order end-to-end with simulated courier ping stream + live map; dine-in QR -> tab -> split -> kitchen board; review via receipt QR
  • Full buddy test, ./buddy lint, typecheck; domain purchase (Chris) + DNS + gateway hostname + prod deploy via CI
9 days ago

i18n is done and live. The locale files had been sitting complete since the scaffold, wired to nothing. All three languages now drive the interface, with a switcher in the nav: English · Deutsch · Nederlands.

The pages are built once and served static, so translation happens in the browser: buddy build:locales compiles locales/*.yml into public/locales/*.json (wired into the deploy's preStart), and elements carrying a data-i18n key are filled from the dictionary.

English stays in the HTML rather than being filled from a dictionary too. A failed fetch, an unknown key, or scripting off then leaves a page that reads correctly in English instead of a page of empty boxes.

The interface is translated; the content is not. The invented restaurant descriptions, menus and reviews stay in English, because machine-translating a menu is how a demonstration starts lying about what a kitchen serves. Real apps draw the same line.

The build step reports keys a locale is missing against English, since a fallback that works is exactly how a missing translation goes unnoticed for months.

Remaining on this issue: the design pre-flight pass, and the CI deploy (#2).

9 days ago

Design pre-flight run against the live site. Checked on desktop and at 375px.

Passes

  • No em-dashes in any user-visible string (the only hits are code comments)
  • Geist with font-display: swap; no Inter, Roboto, Helvetica or Open Sans in any page I wrote
  • No framer-motion, gsap or motion/react; every transition uses a custom cubic-bezier, no linear or ease-in-out
  • Icons are Iconify i-hugeicons-* classes throughout; no hand-rolled SVG paths
  • Double-bezel cards and the button-in-button trailing icon are used consistently
  • No horizontal overflow at 375px on the discover page or a place page; the type filter scrolls sideways rather than wrapping into a blob
  • Demo guards all live: noindex, nofollow, noarchive, noimageindex on every page, a robots.txt that explains itself, and the demonstration banner on every surface

Two bugs the pre-flight caught, both now fixed and deployed:

  • The headline stats counted the sixty places the page renders rather than everything listed, so it said "4 take orders" when twelve do. It now reads 280 places, 58 open now, 12 take orders.
  • openNow had been lost in an earlier edit, leaving a stat card with a label and no number above it. Invisible on desktop scanning, impossible to miss on a phone.
  • The language links were a 30×26px tap target; bumped.

One deviation worth a decision, not a silent pass. AGENTS.md says stx <script> blocks should use signals and composables, never document.*. Thirteen of my client surfaces use vanilla DOM instead. That was not a considered choice at the start so much as the path of least resistance after several stx traps (per-request views dropping awaited values, breaking the build, the client router following links through preventDefault), and it works. Converting them is a real refactor across every interactive page with genuine regression risk, so I have left it and am flagging it rather than quietly claiming the box is ticked. Worth its own issue if you want it done.

9 days ago

Launch gate status.

Done

  • i18n live in English, German and Dutch, with a switcher and a build step that reports keys a locale is missing.
  • Design pre-flight run against the live site on desktop and at 375px. Two bugs caught and fixed (headline stats counting the wrong set; a stat card with a label and no number), plus an undersized tap target.
  • Demo guards are no longer only claims: /admin checks all six against what is actually stored. All holding in production as of this comment.
  • Every surface deployed and verified in the browser rather than by status code.

Blocked on you, both credentials

  1. Stripe test keys. I searched ~/Code/stacks as you suggested: all four .env* files decrypt STRIPE_SECRET_KEY, STRIPE_PUBLIC_KEY and FRONTEND_STRIPE_PUBLIC_KEY to empty strings, and no app or library under ~/Code has one either. The checkout says plainly that no provider is configured; the card form appears with no code change once the keys exist.
  2. DEPLOY_SSH_KEY and DOTENV_PRIVATE_KEY_PRODUCTION as GitHub secrets, for push-to-deploy (#2). Until then deploys are ./buddy deploy from a working copy, which tars the working directory.

Not blocking, but open: the iOS/Android build (#17) needs Xcode and a device run; the code and config are in place.

The domain remains smakelo.stacksjs.com. Nothing here needs the real domain before you want to point it.

9 days ago

The visual pass

Every business has a cover, and it is not a photograph. 268 of these are real places taken from open data. A stock photo of somebody else's dining room under a real restaurant's name fabricates a record of a real place — the same line the reviews guard draws, and a picture crosses it more convincingly than words do. So each cover is generated from the name: a hue hashed from the slug, a second hue for the gradient, an icon picked from the cuisine, and the initials. Stable per business, no two neighbours alike, nothing claiming to be a photograph.

If you want real photography later, the honest sources are the businesses themselves (a claimed listing uploading its own) or the twelve invented partners, where nothing is misrepresented. Both are additive: the cover is a fallback, not a fixture.

Ratings are stars. "4.5" is a number; ★★★★½ is a judgement, and it is the first thing anybody reads on a listing. Drawn as a clipped overlay so a 4.3 is 4.3 wide rather than rounded to the nearest half by the markup.

The hero leads with search, since that is what somebody came to do, and categories are icon tiles built from the results rather than a row of words.

Dishes have glyphs matched from name and description together — "Vongole" says nothing, "Spaghetti, clams, white wine" says plenty — falling back to the business's category so a coffee shop's menu still reads as one.

Reviews have faces: the author's initials in a colour of their own. Which exposed that the twenty seeded reviews had no authors at all, so every avatar read "GU". They have names now, each with a customer row on the reserved .invalid domain, so the "no real contact details" guard still passes.

Saved places, orders and the merchant surface list all carry the same vocabulary.

One trap worth recording: Crosswind generates an icon's CSS from class literals it finds in source, so a class assembled at runtime ('i-hugeicons-' + card.icon) is applied and invisible. The dynamic icons are written out once in a safelist in the head partial.

Accounts

Optional by design. Everything still works from a browser token; an account binds that token to a person so the same list appears on a second device, which is the only thing it buys and the only thing it is sold as. One page rather than two, because somebody who does not know whether they have an account should not have to guess which page to be on.

Signing up after ordering as a guest keeps the guest's saved places, orders, reviews and shares. Starting over is what people expect and resent.

Google and Apple go through the framework's socials package and render only when configuredSocialProviders() says they would work. With no keys the page says so instead of showing a button that fails on the far side. Add them to config/services.ts from env (GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET, and Apple's teamId/keyId/privateKey) and the buttons appear with no code change. Redirect URLs need updating to https://smakelo.stacksjs.com/auth/{provider}/callback.

Mounting auth also narrowed the public surface: STACKS_DEFAULT_ROUTES=auth means this app no longer takes the dashboard storefront, reviews, AI and voice routes it never used.

A bug caught on the way: the bearer lookup queried oauth_access_tokens by the token as sent and matched nothing, because what is stored is a hash. It goes through the framework's findToken now, which hashes before looking up. It was failing closed, but for the wrong reason, and would have stayed broken quietly.

9 days ago

The visual pass is deployed and verified in production.

Live now

  • Search-led hero with the filters folded under it, and category tiles built from the results rather than a hand-kept list.
  • Generated cover art on all 60 discover cards, on place-page headers, on menu items, and on the saved and orders lists. Deterministic per business: a hue hashed from the slug, a cuisine icon, initials.
  • Stars everywhere a rating appears, drawn as a clipped overlay so a 4.3 is 4.3 wide rather than rounded to the nearest half by the markup.
  • Dish glyphs matched from name and description together, falling back to the business's category so a coffee shop's menu still reads as one.
  • Reviewer faces: initials in a colour of their own. The twenty seeded reviews now have authors, so those avatars read BC and JP rather than twenty identical GUs.
  • Icons on the merchant surface list and the balances page.
  • Accounts at /account: sign up, sign in, sign out, with the guest's saved places and orders carried onto the new account. Verified end to end in production.

Why generated art rather than photographs. 268 of these businesses are real places taken from open data. A stock photo of somebody else's dining room under a real restaurant's name fabricates a record of a real place, which is the line the reviews guard already draws — a picture crosses it more convincingly than words do. Real photography stays available from the two honest sources: a claimed listing uploading its own, or the twelve invented partners. Both are additive; the cover is a fallback, not a fixture.

Checked: dark mode inverts the covers correctly, mobile has no horizontal overflow, all six demo guards still pass, and CI is green on every commit.

Two things caught on the way

  • Crosswind generates an icon's CSS from class literals it finds in source, so a class assembled at runtime is applied and invisible. The dynamic ones are safelisted in the head partial.
  • The bearer lookup queried oauth_access_tokens by the token as sent and matched nothing, because what is stored is a hash. It goes through the framework's findToken now.

Google and Apple sign-in are built and dormant pending credentials — see #26.

Sign in to comment on this issue.