v1.8.0 — bucket templates, account safety, honest privacy, and guided UX - #42
Open
ihabkhaled wants to merge 31 commits into
Open
v1.8.0 — bucket templates, account safety, honest privacy, and guided UX#42ihabkhaled wants to merge 31 commits into
ihabkhaled wants to merge 31 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
/ar-latn returned the SPA 404 shell because three routing allow-lists still listed only the twelve original locales, and Arabic Franco was app-only: the landing page had no Franco copy at all. Franco is now the thirteenth public locale rather than an app-only register. Adding the full marketing translation — ten pages, the UI chrome, the three system pages, and the contact form — lets the locale drop the fallback that quietly redirected Franco readers to formal Arabic. - Route `ar-latn` in the browser prefix pattern (before `ar`, so the longer segment wins), the static dev server, and both vercel.json rewrites. - Derive the sitemap URL and locale counts from the catalog instead of the hardcoded 120/12, so the next locale does not need a validator edit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five to eight tours now cover every one of the nineteen screens, so each one explains what it does rather than dropping a single hint and disappearing. Each screen's copy moves into its own use-*-tour.hook.ts. Fourteen containers were carrying their step arrays inline in JSX, which was already crowded at two entries and unreadable at seven; the five screens that had hooks keep the callback-ref spotlight targets they already used. Steps that describe how a screen behaves centre their card instead of pointing at a control, which also keeps them readable on a narrow phone. Adds a locale-switch regression test that drives the in-app language selector to Arabic Franco and follows it through the redirect and a reload. Verified to fail against the previous single-part locale prefix matcher, which matched the `ar` in `/ar-latn`, rejected the tail, and sent Franco readers back to English on the next load. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Switching to Arabic Franco while signed in snapped back to the previous language on the next load. The deployed Firestore rules gate the profile `locale` field against an allow-list that predated Franco, so the write was refused with PERMISSION_DENIED, the stored profile kept its old language, and the post-login profile load navigated the reader back to it. Confirmed against the live project: writing `fr` returned 200 while `ar-Latn` returned 403. The rule already listed `ar-Latn` in this repo but had never been deployed, so nothing in the codebase revealed the drift. Rules tests now assert that every locale the app offers is accepted and that a wrongly-cased `ar-latn` is refused, which turns the next such drift into a red gate. Separately, the product name read "Gama3 Orderak" in all eleven locales other than English and Arabic. Each locale now says "collect your order" in its own language across the app shell, the public header and footer, `og:site_name`, and the structured data, so the JSON-LD name matches the rendered one. The brand name moves from the single global `site.brandName` into per-locale UI copy, and the type now requires it so a locale cannot silently omit it. The square brand mark had no `flex: none` and was being squeezed out of shape by the longer name; it now holds its size and the name wraps beneath it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Seven navigation labels never fitted beside the brand, language menu, and call to action. Measuring the natural width the bar needs showed 1610px in French and over 1470px in five other locales, so on any normal screen the row was being squeezed until labels broke mid-word and the header grew to three lines. The bar now carries only the brand, the language menu, and the call to action, with every destination in a collapsible list at all widths. The full product name is always shown — an earlier ellipsis hid it, which defeats the point of naming the product in the reader's language. Three further faults surfaced while measuring, all fixed here: - The call to action had no `flex: none`, so it shrank and wrapped "Eftah el app" over three lines into a tall block. - The dropdown panels set `display` unconditionally, which cancels the browser's hiding of closed <details> content. Both panels stayed laid out off-screen while closed and gave narrow phones a horizontal scrollbar. - The narrow hero used `grid-template-columns: 1fr`. A plain `fr` track takes its minimum from content, so "Gruppenbestellungen" stretched the column past the viewport. `minmax(0, 1fr)` lets it shrink. The last two predate this branch and reproduce on production today. Verified across all thirteen locales at nine widths from 320px to 1600px: the header is a single row except at 320px, the name is never clipped, and no page scrolls sideways. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The public pages followed the operating system with no way to override it. A switch now sits beside the language menu, with the icon showing the theme you would move to rather than the one you are in. An inline script applies the stored choice before first paint, so a reader who picked light on a dark machine never sees a dark flash while the page loads. It is inline on purpose: these pages ship no external scripts, and the validator enforces that. Both directions had to be made to work. The existing dark rules lived in a prefers-color-scheme block, so scoping only the custom properties left the background rules applying on a dark system and an explicit light choice had no visible effect. Every selector in that block is now scoped with :not([data-theme='light']). Browser access goes through a new platform adapter rather than the module, and the React public shell drives the same markup through a hook so client-side navigation matches the prerendered output. Verified on both a light and a dark simulated system: initial state follows the OS, either choice survives a reload and a navigation, and the switch is present and labelled in all thirteen locales. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The friends screen carried six sections at once; sharing carried four. Both now follow the settings-hub shape. /social is a hub — counts up top, one row per destination — with three subpages: /social/friends (search plus the friends list), /social/requests (friend, group, and bucket invitations, with a pending count on the hub row), and /social/groups (creation, editing, membership). Social notifications now deep-link to the right subpage instead of the old catch-all screen, in both the local gateway and the Cloud Functions fan-out. /buckets/:id/share keeps the invite code, enable, and freeze controls; member roles/permissions and the activity log move to /share/members and /share/activity behind rows that carry the member count. The settings link-row component was about to gain its third copy, so it moves to src/shared/ui as LinkRow with its styles in the global sheet; settings now consumes the shared one. Also in this commit, three green-gate fixes the PR run surfaced: - Branch All Gates Green failed because the pre-commit eslint --fix touched a spec one byte after the knowledge build, leaving stale .ai manifests; rebuilt in the same commit that changes sources. - The deployed-callable smoke test no longer fails on a persistent HTTP 429: throttling can only come from an endpoint that exists (a missing function returns 404), so it downgrades to a warning while 404/5xx still fail, and probes are spaced so five callables do not trip the same quota. - The notification-mirroring e2e raced the subscription baseline: the first payload is never mirrored, so on a slow runner the test's notification was folded into it and the tray stayed empty. The test now proves the baseline landed (badge visible) before backgrounding, like its sibling always did. Validation: 195/195 across chromium, mobile-chrome, and tablet-chrome — the exact matrix the E2E gate runs — plus the unit, i18n, knip, and typecheck gates, and a browser walkthrough of the new hub and subpages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three related changes to how a build identifies and describes itself.
Build numbers now count builds of one version. They came from
github.run_number, a repository-wide counter that never resets, so `1.8.0-347`
said nothing about how many times 1.8.0 had been built. The number is derived
from the release tags that already exist, which makes it per-version and
self-healing: it increases within a version and restarts at 0 when the version
changes, with nobody remembering to reset it. Branch prereleases follow the
same rule as `X.Y.Z-dev.<n>`.
Releases now publish the version's release notes. A prerelease body used to be
one generated sentence — "Automated prerelease from branch X. Every mandatory
branch gate passed for commit Y." — which describes the process and leaves a
reader no way to know what shipped. Every release body is now composed from
release-notes/vX.Y.Z.md plus that build's own provenance.
Pull requests now carry the same notes as their description, written from the
`## Highlights` section, alongside a link to the APK built from the exact head
commit. The job rewrites only the block between its markers, so anything an
author or reviewer types around it survives every later push.
Written down as a skill (skills/write-release-notes.md), a rule
(rules/20-release-gates.md forbids a generated release body and a
repository-wide build counter), and operations docs.
Two gaps found while doing this:
- tests/tooling/*.test.mjs was never executed. vitest.config.ts includes only
.test.{ts,tsx}, and these use node:test, which vitest does not provide. The
pre-existing versioning test had never run in CI. Added npm run test:tooling
and wired it into both gate workflows, so the nine tooling tests now gate.
- A git worktree under .claude/ was being linted, format-checked, and indexed
as a second copy of the repository — 1806 files instead of 943, which would
have failed the manifest diff gate in CI where no such directory exists.
Excluded from ESLint, the format checker, the knowledge build, and git.
Co-Authored-By: Claude Opus 5 <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.
Delivers the eleven requested v1.8.0 items on top of
main(1.7.4 → 1.8.0).What changed
session from a shared bucket, seeds the same members, and notifies each of
them. The template is never renamed or consumed, so the same menu runs every
week. Built on the existing ADR-0009 order-session architecture, which was
complete but unreachable from the UI.
removed until they verify, in both Firebase and local-device modes.
typing
14yields14, not014.each section shows its own placeholder, and the two bucket collections
resolve independently.
while the app is backgrounded, and a tap opens the destination. Permission is
only ever requested from a deliberate user action.
account pages.
device-local diagnostics window that never leaves the device, with a visible
count and a clear action. Consent roams with the profile.
systemtheme follows live OS changes.
Settings → Preferences.
Evidence
Pixel 7, and iPad Mini; Firefox cross-browser suite green.
analyticsConsent).fixed two real defects: the notification prompt stacked on top of the
first-run tour, and could interrupt a half-filled form.
Notes for review
@capacitor/local-notificationsexpands the Android permission surface(
POST_NOTIFICATIONS,RECEIVE_BOOT_COMPLETED,WAKE_LOCK) via manifestmerging; recorded in
docs/migration/native-security-audit.md.google-services.json, and APNs assets that live outside this repository.Prerequisites:
docs/operations/push-notifications.md.so desktop WebKit and mobile Safari rely on this pipeline's Linux runners.
.env.examplegained the previously undocumentedCONTACT_EMAIL_PROVIDERkey to close a drift with.env."Gama3 Orderak" — left for an owner decision rather than changed unilaterally.
🤖 Generated with Claude Code
v1.8.0
Build: the branch prerelease publishes the APK once every gate passes.
Highlights
from a shared bucket, seeds the same members automatically, and notifies each
of them — the template itself is never renamed, consumed, or duplicated, so
the same menu can run every week.
placeholder, so typing
14produces14instead of014.ends, instead of signing out on a single mis-tap.
nothing is deleted until those credentials verify, in both Firebase and
local-device modes.
preferences, privacy, security, and data & account.
/socialis a hub with counts andthree rows, and search + friends list, pending requests, and groups each get
their own page. Social notifications deep-link to the right subpage — an
invitation opens Requests, a group change opens Groups.
controls, while member roles/permissions and the activity log move to their
own pages behind labelled rows with a member count.
smoke probes: a persistent HTTP 429 can only come from an endpoint that
exists (a missing function returns 404), so it is recorded as a warning while
a real 404/5xx still fails. Probes are also spaced out so five callables do
not trip the same quota.
systemtheme now follows live operating-system light/dark changes.to a capped device-local window that never leaves the device, and the privacy
screen shows the exact stored count with a clear action.
immediately while individual sections show their own placeholders, and the two
bucket collections now resolve independently of each other.
the app is in the background, and tapping one opens its destination in the
app. Permission is only ever requested from a deliberate user action.
the bucket editor, sharing, collaboration, joining, orders, order details,
order creation, order rounds and their details, friends, and every settings
page. Each screen walks through five to eight explanations rather than a
single hint, dims the page behind it, and highlights the element being
described where there is one. Three deliberate exits: Got it retires that
screen's tour, Skip ends only this visit, Skip all silences every
screen. All of them replay from Settings → Preferences. Tour copy lives in a
per-screen
use-*-tour.hook.tsrather than inline in the containers.in Latin script is hand-written idiomatic copy — "tasgeel el khoroog", not a
transliteration table — across the app UI, the six feature catalogues, Firebase
error messages, password-reset email, and all ten public marketing pages. It is
selectable in Settings and served at
/ar-latn, with its own sitemap, RSS feed,and
hreflangentries.Cloud Messaging even with the app closed. Tokens live under
users/{uid}/pushTokens, are pruned when they go stale, and are removed onsign-out so a shared phone never receives the previous account's rounds.
governs local diagnostics — neither loads until consent is granted.
to the operating system, an explicit choice overrides it in either direction,
and the choice is applied from an inline script before first paint so no page
flashes the wrong palette. The prerendered pages stay free of external
scripts.
menu, and the call to action, with all seven destinations in a collapsible
list. Seven translated labels never fitted beside them — the bar reflowed
into three rows in most locales, and the name was being cut short. Fixed
alongside three layout faults found while measuring it: a call-to-action that
wrapped mid-label, dropdown panels that claimed layout space while closed and
gave narrow phones a horizontal scrollbar, and a hero grid whose
1frtracktook its minimum from content so one long German compound word widened the
page. Verified across all thirteen locales at nine widths.
"جمع أوردرك" in Arabic, "Gama3 Orderak" in Franco, and its own rendering in
the other ten — across the app shell, the public header and footer, the
og:site_nametag, and the structured data. It used to read the Franco namein every language but English and Arabic. The square brand mark no longer
shrinks to make room for a long name; the name wraps instead.
ads.txtgenerated from
ADSENSE_PUBLISHER_ID. The logged-in app shell and all noindexsystem pages stay ad-free, and
npm run public:validateenforces it.Full notes:
release-notes/v1.8.0.md