Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
bab823f
docs: SafeBite PWA design spec (with plan audit) and Plan 1 foundatio…
Bogdan0708 Sep 20, 2026
24e9eac
docs(plan): use node16 module resolution so package export maps resolve
Bogdan0708 Sep 20, 2026
7465ca0
chore: untrack compiler caches, remove duplicate Firebase config and …
Bogdan0708 Sep 20, 2026
ec62a3a
chore: emulator-only Firebase config and household security rules
Bogdan0708 Sep 20, 2026
f7e4f2a
test: functions package scaffold and Firestore rules tests for househ…
Bogdan0708 Sep 20, 2026
81f6e8c
docs(plan): vitest config as .mts; drop unused expect import in rules…
Bogdan0708 Sep 20, 2026
01efa5b
test(functions): load vitest config as ESM (.mts); drop unused import
Bogdan0708 Sep 20, 2026
9e9bcd2
feat(functions): requireMember helper and whoami callable with emulat…
Bogdan0708 Sep 20, 2026
0fab0d2
docs(plan): emulator cold-start headroom (discovery timeout, vitest t…
Bogdan0708 Sep 20, 2026
72e0afc
test(functions): give emulator cold start headroom; import v2/options…
Bogdan0708 Sep 20, 2026
c38158b
test(functions): warm the functions emulator before callable tests
Bogdan0708 Sep 20, 2026
df77f46
docs(plan): warm the functions emulator before callable tests (Task 4…
Bogdan0708 Sep 20, 2026
0023109
test(functions): bound the emulator warm-up and require the callable …
Bogdan0708 Sep 20, 2026
194af47
docs(plan): bounded warm-up helper signature
Bogdan0708 Sep 20, 2026
181d180
feat(functions): emulator-only seed for pilot accounts and household
Bogdan0708 Sep 20, 2026
04372f4
docs(plan): seed narrows user-not-found; safe env restore in test
Bogdan0708 Sep 20, 2026
55ffecc
fix(functions): seed only creates users that are actually missing
Bogdan0708 Sep 20, 2026
4836061
feat(web): Vite scaffold, Firebase emulator wiring, membership resolver
Bogdan0708 Sep 20, 2026
7851bdb
docs(plan): scaffold drift guidance for Task 6 (strict, engines, left…
Bogdan0708 Sep 20, 2026
953297b
chore(web): explicit strict mode, drop scaffold leftovers, pin Node e…
Bogdan0708 Sep 20, 2026
d90645b
feat(web): auth gate with sign-in, not-invited screen, and app shell
Bogdan0708 Sep 20, 2026
bff4e93
docs(plan): explicit Testing Library cleanup in test setup
Bogdan0708 Sep 20, 2026
0661e11
feat(web): distinguish membership-check failures from non-membership;…
Bogdan0708 Sep 20, 2026
0515b41
docs(plan): mirror Task 7 error state, retry screen, SubmitEvent, unm…
Bogdan0708 Sep 20, 2026
568d399
test(web): Playwright auth flows against Firebase emulators
Bogdan0708 Sep 20, 2026
1692c1a
docs(plan): mirror Task 8 Playwright config and emulator warm-up glob…
Bogdan0708 Sep 20, 2026
46bbd1a
docs(plan): README corrections for Task 9 (WSL path note, unit test c…
Bogdan0708 Sep 20, 2026
17d04bd
ci: GitHub Actions for typecheck, unit, emulator and browser tests
Bogdan0708 Sep 20, 2026
59e815e
docs(spec): assign PWA app shell to Plan 2; record Plan 2 pre-work fr…
Bogdan0708 Sep 20, 2026
e60090f
fix: pin identity and default-deny with tests; predeploy hooks; prod …
Bogdan0708 Sep 20, 2026
ac7e5cf
docs(plan): Plan 1b hardening (account switching, build-time config v…
Bogdan0708 Sep 20, 2026
e81ddbe
test(web): wait for sign-out before switching accounts; stress script…
Bogdan0708 Sep 20, 2026
41212ac
feat(web): reject undeployable Firebase configuration at build time a…
Bogdan0708 Sep 21, 2026
3154d31
docs(plan): mirror Task 2 deviations (nodenext import extension, READ…
Bogdan0708 Sep 21, 2026
1aecc1e
test: abort stalled warm-up requests; always upload browser artifacts…
Bogdan0708 Sep 21, 2026
df10ce5
test: clamp warm-up attempts to the remaining time budget
Bogdan0708 Sep 21, 2026
e5f43f1
docs(plan): mirror clamped warm-up loops (Task 3 fix round 1)
Bogdan0708 Sep 21, 2026
7db2c16
docs(spec): record the path-scoped guardrail exception for the deploy…
Bogdan0708 Sep 21, 2026
4a004f0
docs(spec): Plan 2 pre-work items from the Plan 1b final review
Bogdan0708 Sep 21, 2026
7d1d734
fix: assert validator rejection in CI; validate Vite's own env; tidy …
Bogdan0708 Sep 21, 2026
4d5af30
fix(web): refuse non-production builds; key the startup guard on a bu…
Bogdan0708 Sep 21, 2026
517d236
docs(plan): Plan 2a — pre-work, misconfiguration screen, PWA app shel…
Bogdan0708 Sep 21, 2026
89cfd5b
chore: typecheck functions tests; CI-conditional emulator timeouts; L…
Bogdan0708 Sep 21, 2026
d13eab4
feat(rules): read household membership from resource.data, no extra g…
Bogdan0708 Sep 21, 2026
508b998
test(web): pin the auth provider's generation race and unsubscribe-on…
Bogdan0708 Sep 21, 2026
2572a5e
feat(web): cancellable callables; one whoami request under StrictMode…
Bogdan0708 Sep 21, 2026
f57bf16
fix(web): drain the underlying promise when abortable() is called wit…
Bogdan0708 Sep 21, 2026
fbd3528
feat(web): validator shape checks for api key, app id and auth domain…
Bogdan0708 Sep 21, 2026
938660c
feat(web): misconfigured-build screen; bootstrap never loads Firebase…
Bogdan0708 Sep 21, 2026
8e5da38
feat(web): installable PWA shell — icon set, manifest, Apple meta tag…
Bogdan0708 Sep 21, 2026
f5d2597
test(web): preview-bundle PWA suite (manifest, worker, offline reload…
Bogdan0708 Sep 21, 2026
6ae6b28
fix(web): final-review fixes — load-failed screen, e2e typecheck, SW …
Bogdan0708 Sep 21, 2026
b054ea7
docs(spec): carry the Plan 2a final-review deferrals into Plan 2b/3/5…
Bogdan0708 Sep 21, 2026
4c415d3
docs(plan): Plan 2a-h — worker-update hardening for the audit's P2 fi…
Bogdan0708 Sep 21, 2026
c97aec2
test(web): same-origin upgrade suite; records the audit's P2 worker-u…
Bogdan0708 Sep 21, 2026
7ec6826
fix(web): non-deployable builds emit a self-destroying service worker…
Bogdan0708 Sep 21, 2026
030dab8
fix(web): purge service-worker state on misconfiguration and reload a…
Bogdan0708 Sep 21, 2026
b9fde67
fix(web): purge tolerates individual unregister/cache-delete failures…
Bogdan0708 Sep 21, 2026
4825d86
fix(web): final-review fixes — purge reload guard, worker-decision in…
Bogdan0708 Sep 21, 2026
17ebe57
docs(spec): carry the Plan 2a-h final-review deferrals into Plan 2b p…
Bogdan0708 Sep 21, 2026
27ca7ac
docs(spec): add §3.5 Plan 2b design — restaurant records, evidence, u…
Bogdan0708 Sep 21, 2026
27c0cce
docs(spec): revise §3.5 Plan 2b design after the design audit (F1–F7)…
Bogdan0708 Sep 21, 2026
d511593
docs(plan): Plan 2b — restaurant records and evidence (12 tasks); com…
Bogdan0708 Sep 21, 2026
e1f58b0
build(web): committed synthetic .env fixtures with a --mode identity …
Bogdan0708 Sep 21, 2026
158da03
build(web): provenance stamp per build, fresh-dist checks in the Play…
Bogdan0708 Sep 21, 2026
9661a04
fix(web): precache every shell file once; self-destroying builds emit…
Bogdan0708 Sep 21, 2026
0cfff94
feat(web): prompt-mode service worker with a per-tab Reload banner; t…
Bogdan0708 Sep 21, 2026
e5c02b2
fix(web): banner no longer pushes the tab bar off-screen; pin update-…
Bogdan0708 Sep 21, 2026
fdfa5eb
feat(web): records domain — types, UTC calendar dates, evidence state…
Bogdan0708 Sep 21, 2026
f7cca99
feat(rules): household restaurants — member-only, validated fields, v…
Bogdan0708 Sep 21, 2026
8645b5b
feat(rules): immutable evidence claims — validated fields, URL policy…
Bogdan0708 Sep 21, 2026
a6f66de
fix(rules): de-flake the two-days-ahead claim rejection test
Bogdan0708 Sep 21, 2026
97ae5a1
feat(web): records repository — snapshot listeners with read states, …
Bogdan0708 Sep 21, 2026
f9b9aad
feat(web): Saved tab lists the household's restaurant records with re…
Bogdan0708 Sep 21, 2026
45cd071
feat(web): restaurant form — create/edit with a draft kept apart from…
Bogdan0708 Sep 21, 2026
4e5575b
fix(web): restaurant form — single Reload draft when conflict coincid…
Bogdan0708 Sep 21, 2026
a529202
feat(web): restaurant detail with evidence by kind (unknown/current/n…
Bogdan0708 Sep 21, 2026
0fd3b0d
test(web): seven browser scenarios for restaurant records (evidence s…
Bogdan0708 Sep 21, 2026
a0ec809
fix(web): plan 2b final-review fixes — claims read-state gating, stal…
Bogdan0708 Sep 21, 2026
0b8c313
docs(spec): add §3.6 Plan 3 design — discovery through functions (two…
Bogdan0708 Sep 22, 2026
e34e1fa
docs(plan): Plan 3 — discovery through functions (10 tasks: abortable…
Bogdan0708 Sep 22, 2026
586b6c3
feat(web): abortable keeps the abort reason; anySignal and isTimeoutE…
Bogdan0708 Sep 22, 2026
1cf3da4
fix(web): abortable and isTimeoutError match the plan's contract with…
Bogdan0708 Sep 22, 2026
28e0346
feat(functions): discovery domain — result types, input parsing, UTC …
Bogdan0708 Sep 22, 2026
c15c912
feat(functions): PlacesProvider interface, ProviderError, fixture pro…
Bogdan0708 Sep 22, 2026
ac9b992
feat(functions): Google Places (New) adapter with a five-field mask a…
Bogdan0708 Sep 22, 2026
91b485f
feat(functions): searchDestination and searchNearby — member-only, fa…
Bogdan0708 Sep 22, 2026
84bedd7
feat(functions): fixture secret for emulator runs, seeded discovery c…
Bogdan0708 Sep 22, 2026
5cb1b84
feat(web): discovery callables with error classification; sequence-nu…
Bogdan0708 Sep 22, 2026
71e8d87
feat(web): Discover page — destination search and Near me on tap, eve…
Bogdan0708 Sep 22, 2026
a78185e
feat(web): create a restaurant record from a Discover result — router…
Bogdan0708 Sep 22, 2026
fe117c6
test(web): eleven browser scenarios for discovery (results, empty, ki…
Bogdan0708 Sep 22, 2026
9afda17
fix: explicit callable region/maxInstances with an endpoint test; bou…
Bogdan0708 Sep 22, 2026
898d2a8
fix: e2e decodes every stored field, CI budget/retry and Places guard…
Bogdan0708 Sep 22, 2026
7e90e74
docs(audit): Plan 3 execution ledger — rulings, per-task review outco…
Bogdan0708 Sep 22, 2026
b5249c4
fix(discover): tie a pending location request to its search intent
Bogdan0708 Sep 22, 2026
e229a72
fix(discovery): restrict destination search to restaurants
Bogdan0708 Sep 22, 2026
a1c7808
fix(discovery): log fixed diagnostics only, verified against the real…
Bogdan0708 Sep 22, 2026
0b24ad9
docs(spec): correct pricing, fixture provenance, and record the audit…
Bogdan0708 Sep 22, 2026
858511c
fix(discover): carry only the Google place id to the record form
Bogdan0708 Sep 22, 2026
0bdc8f4
docs(spec): record the owner ruling on audit S1/F2
Bogdan0708 Sep 22, 2026
952b3a4
fix(discovery): widen venue types to cafés/bakeries/bars/takeaways, l…
Bogdan0708 Sep 22, 2026
4bcd608
docs(spec): record Fix F's venue-type widening and error-status logging
Bogdan0708 Sep 22, 2026
ef1934d
docs(audit): ledger addendum — independent-review response (fixes A–F…
Bogdan0708 Sep 22, 2026
37cc46b
fix(records): bind deletion intent and disclose cached evidence
Bogdan0708 Sep 23, 2026
6aa4976
fix(discovery): distinguish destinations from venue names
Bogdan0708 Sep 23, 2026
5be7100
docs(audit): preserve pre-merge reviews and landing evidence
Bogdan0708 Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
10 changes: 5 additions & 5 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"projects": {
"default": "safebite-production-13ba1"
}
}
{
"projects": {
"default": "demo-safebite"
}
}
21 changes: 21 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Line endings. Everything the PWA touches is LF. The legacy Swift tree (SafeBite/,
# SafeBiteTests/, Package.swift, Config.xcconfig.template, docs/) is deliberately not
# listed: it stays byte-for-byte as it is in history.
web/** text eol=lf
functions/** text eol=lf
planning/** text eol=lf
.github/** text eol=lf
/README.md text eol=lf
/CLAUDE.md text eol=lf
/AGENTS.md text eol=lf
/firebase.json text eol=lf
/.firebaserc text eol=lf
/firestore.rules text eol=lf
/firestore.indexes.json text eol=lf
/package.json text eol=lf
/package-lock.json text eol=lf
/.gitignore text eol=lf
/.gitattributes text eol=lf
*.png binary
*.ico binary
*.webp binary
167 changes: 167 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: ci

on:
push:
branches: [main]
pull_request:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
web-and-functions:
runs-on: ubuntu-latest
# 35: the browser suite grew from 12 to 23 scenarios, its globalTimeout is now 900s, and scenario 6
# alone waits ~25s by design.
timeout-minutes: 35
env:
CI: "true"
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: |
package-lock.json
web/package-lock.json
functions/package-lock.json

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"

- name: Install dependencies
run: |
npm ci
npm ci --prefix web
npm ci --prefix functions

- name: Guardrail — no production project id in tracked files
run: |
if git grep -n "safebite-production-13ba1" -- ':!SafeBite/**' ':!*.md' ':!docs/**' ':!.github/workflows/ci.yml' ':!web/src/config/firebaseEnv.ts' ':!web/src/config/firebaseEnv.test.ts'; then
echo "Production project id found in tracked files"; exit 1
fi

- name: Guardrail — discovery never caches Places data or carries a key
run: |
if git grep -n "localStorage\|sessionStorage\|indexedDB" -- web/src/discover; then
echo "Discovery code touches browser storage — Places results must never be cached client-side"; exit 1
fi
# web/src/config legitimately mentions API key shapes (the config validator and its test);
# committed .env.* fixtures live outside web/src and are not covered by this grep.
if git grep -n "AIza" -- web/src ':!web/src/config/**'; then
echo "Looks like a Google API key literal in web/src"; exit 1
fi
# *.md excluded: AGENTS.md documents the unrelated legacy iOS GOOGLE_PLACES_API_KEY var, whose
# name contains this substring. functions/.secret.local.example legitimately sets the fixture value.
if git grep -n "PLACES_API_KEY=" -- ':!*.md' | grep -v "PLACES_API_KEY=fixture"; then
echo "A non-fixture PLACES_API_KEY value is committed"; exit 1
fi

- name: Typecheck
run: npm run typecheck

- name: Unit tests (web)
run: npm run test:unit

- name: Cache Firebase emulators
uses: actions/cache@v4
with:
path: ~/.cache/firebase/emulators
key: firebase-emulators-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

- name: Functions + rules tests (emulators)
run: npm run emu:test

- name: Install Playwright Chromium
run: cd web && npx playwright install --with-deps chromium

- name: Browser tests (emulators)
run: npm run emu:e2e

- name: Build (compile check, unvalidated)
run: npm run build

- name: Deployable build is rejected without Firebase configuration
env:
VITE_FIREBASE_API_KEY: ""
VITE_FIREBASE_AUTH_DOMAIN: ""
VITE_FIREBASE_PROJECT_ID: ""
VITE_FIREBASE_APP_ID: ""
run: |
set +e
out=$(npm --prefix web run build 2>&1)
status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$out"; echo "Deployable build succeeded without configuration — validator is broken"; exit 1
fi
if ! echo "$out" | grep -q "Firebase configuration is not deployable"; then
echo "$out"; echo "Build failed, but not because of the config validator"; exit 1
fi
echo "Validator rejected the unconfigured deployable build as expected"

- name: Non-production build is rejected (NODE_ENV=development, compile-only)
env:
NODE_ENV: development
SAFEBITE_UNVALIDATED_BUILD: "1"
VITE_FIREBASE_API_KEY: demo-api-key
VITE_FIREBASE_AUTH_DOMAIN: localhost
VITE_FIREBASE_PROJECT_ID: demo-safebite
VITE_FIREBASE_APP_ID: demo-app-id
VITE_USE_EMULATORS: "true"
run: |
set +e
out=$(npm --prefix web run build 2>&1)
status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$out"; echo "NODE_ENV=development compile-only build succeeded — the non-production guard is broken"; exit 1
fi
if ! echo "$out" | grep -q "Refusing a non-production Vite build"; then
echo "$out"; echo "Build failed, but not because of the non-production guard"; exit 1
fi
echo "Non-production build rejected as expected"

- name: Synthetic build refuses an ambient VITE_ override (fixture identity)
env:
VITE_FIREBASE_PROJECT_ID: not-the-fixture
run: |
set +e
out=$(npm --prefix web run build:preview 2>&1)
status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$out"; echo "build:preview succeeded with an ambient VITE_FIREBASE_PROJECT_ID — the fixture check is broken"; exit 1
fi
if ! echo "$out" | grep -q "is not hermetic"; then
echo "$out"; echo "Build failed, but not because of the fixture check"; exit 1
fi
echo "Fixture identity check rejected the override as expected"

- name: Build the three synthetic test bundles (preview, preview-v2, boot-guard)
run: npm --prefix web run build:e2e

- name: Compile-only bundle refuses to start in a browser
run: npm --prefix web run e2e:boot-guard

- name: PWA shell — manifest, service worker, offline reload (preview bundle)
run: npm --prefix web run e2e:preview

- name: Service worker upgrades and update prompt (upgrade suite)
run: npm --prefix web run e2e:upgrade

- name: Upload Playwright artifacts (report, traces, screenshots, videos)
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts
path: |
web/playwright-report
web/test-results
if-no-files-found: ignore
retention-days: 14
150 changes: 86 additions & 64 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,64 +1,86 @@
# Xcode
*.xcodeproj/xcuserdata/
*.xcworkspace/xcuserdata/
*.xcodeproj/project.xcworkspace/xcshareddata/
*.xccheckout
*.moved-aside
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
DerivedData/
build/

# Swift Package Manager
.build/
.swiftpm/
Package.resolved

# CocoaPods
Pods/

# Carthage
Carthage/Build/

# Firebase - NEVER COMMIT THESE
**/GoogleService-Info.plist
**/google-services.json

# API Keys and Secrets
*.xcconfig
!*.xcconfig.template
secrets/
.env
.env.*

# OS Files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Thumbs.db

# IDEs
.idea/
*.swp
*.swo
*~

# Test outputs
*.xcresult
coverage/

# Fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output

# Archives
*.xcarchive
*.xcconfig
service-account.json
node_modules/
# Xcode
*.xcodeproj/xcuserdata/
*.xcworkspace/xcuserdata/
*.xcodeproj/project.xcworkspace/xcshareddata/
*.xccheckout
*.moved-aside
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
DerivedData/
build/

# Swift Package Manager
.build/
.swiftpm/
Package.resolved

# CocoaPods
Pods/

# Carthage
Carthage/Build/

# Firebase - NEVER COMMIT THESE
**/GoogleService-Info.plist
**/google-services.json

# API Keys and Secrets
*.xcconfig
!*.xcconfig.template
secrets/
.env
.env.*

# OS Files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Thumbs.db

# IDEs
.idea/
*.swp
*.swo
*~

# Test outputs
*.xcresult
coverage/

# Fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output

# Archives
*.xcarchive
*.xcconfig
service-account.json
node_modules/

# Compiler caches
.clang-module-cache/

# Web app / functions
web/dist/
web/dist-boot-guard/
web/dist-preview/
web/dist-preview-v2/
web/test-results/
web/playwright-report/
functions/lib/
.emulator-data/
firebase-debug.log
firestore-debug.log
ui-debug.log
*.log
!/web/.env.development
!/web/.env.preview
!/web/.env.preview-v2
!/web/.env.boot-guard
functions/.secret.local
Loading
Loading