Release v0.6 — stable installable PWA, FOSS (Apache 2.0), website + docs overhaul#603
Conversation
Systematic audit of every MCP tool against the live Supabase schema. Fixes 10 broken tools, bringing pass rate from 16/26 to 25/26. Schema mismatches fixed: - jobs.customer_name -> jobs.customer (column renamed) - jobs: remove non-existent started_at/paused_at/completed_at/resumed_at/priority columns - parts: fix status enum pending->not_started, customer_name->customer in join - parts: current_stage_id -> current_cell_id - operations: remove non-existent started_at/paused_at timestamp fields - operations: fix status enum paused->on_hold, cancelled removed - issues: fix status enum open/in_progress/resolved -> pending/approved/rejected - issues: fix ncr_category enum to match DB (material_defect/dimensional/surface_finish/process_error/other) - issues: fix ncr_disposition enum (remove repair, not in DB) - issues: remove ncr_number column reference (doesn't exist) - issues: fix ambiguous profiles join -> use operations join instead - substeps: description->name, completed->status (text), remove get_next_substep_sequence RPC - cells: enforce_limit->enforce_wip_limit, show_warning->show_capacity_warning - dashboard: replace tasks table query with operations (no tasks table) - dashboard: fix get_production_metrics to use operation_quantities instead of non-existent operation columns - scrap: fix material join (parts.materials.name -> parts.material) - scrap_reasons: add includeDeleted flag (no deleted_at column) Error handling improvements: - wrapError now handles Supabase PostgrestError objects (plain objects with .message) - All raw `throw error` replaced with `throw databaseError(...)` for proper error messages - create_job: add tenant_id from TENANT_ID env var (NOT NULL constraint) Removed: - tasks module (no backing table in current schema) Tests: 90/90 pass, build clean, 25/26 live stdio tools verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refresh the docs site for the v0.5 line and announce the native Android
and iOS apps that are now in development.
- Landing pages (EN/DE/NL): SaaS-style platform availability strip
("Native in Browser", Android Coming Soon, iOS Coming Soon), updated
hero tagline, dedicated Native Mobile Apps section with feature
bullets and "In Development" badges, refined Current Status cards
pointing at v0.5.x stable + native apps coming next.
- 22 -> 24 REST endpoints across EN/DE/NL index pages and ERP
integration page; mention pluggable planning adapters (FrePPLe,
Odoo) alongside MCP server, MQTT, webhooks.
- Scheduling feature page: new "Planning Adapters (v0.5)" section
covering FrePPLe (production-ready) and Odoo MRP (scaffold), the
shared adapter interface, and the createPlanningAdapter factory.
- ERP integration page: new "Planning Adapters" integration option
table.
- MQTT/Webhooks page: new "Reliability (v0.5)" section covering
exponential backoff retry, per-attempt timeout, circuit breaker,
dead letter logging, and injectable transport.
- Introduction pages (EN/DE/NL): wire up v0.5 talking points
(24 endpoints, MCP stdio + Streamable HTTP, planning adapters,
MQTT/webhook hardening).
- Changelog + roadmap: soften "development on hold" messaging now
that native apps are in development; roadmap gains an
"In Development" section for the Android and iOS apps with notes
on offline support, fast cold start, and shared backend/API.
Adds vite-plugin-pwa + workbox so Eryxon Flow installs as a standalone desktop app on macOS (Safari 17+ "Add to Dock" or Chrome/Edge install) with its own Launchpad/Dock tile and icon, alongside iOS/Android home screen and Windows Start menu support. - Web manifest (id, scope, start_url, theme/background colors, display: standalone) with full PNG icon set + maskable variant generated from public/pwa-icon.svg via @vite-pwa/assets-generator - Service worker (autoUpdate) precaches the SPA shell, NetworkFirst on /env.js so self-hosted runtime config is never stale, CacheFirst on fonts - Apple/PWA meta tags in index.html; CSP gains manifest-src 'self' - SW/manifest/env.js cache headers wired in nginx.conf, vercel.json and public/_headers so installs always pick up new versions - npm run pwa:assets regenerates icons from the SVG source
Wraps the operator UI as a Capacitor WebView app (iPhone + iPad) and adds a touch-first `/m/*` shell that the web bundle also serves as a Safari PWA. Same React 18 bundle, same data layer — just a new chrome optimized for the shop floor. What's new - Capacitor 6 config + iOS plugins (haptics, status bar, splash, keyboard, push, ML Kit barcode scanner, native biometric). - `src/lib/native/*` — Capacitor wrappers that no-op gracefully on the web. - `src/components/mobile/*` — adaptive shell: bottom tab bar on iPhone, split-view master/detail on iPad, plus iOS-feel `PullToRefresh`, `SwipeRow`, `MobileTopBar`, `SafeArea`. - `src/pages/mobile/*` — operator-focused screens: work queue, op detail with bottom-anchored Start/Stop/Complete, QR/barcode scanner, activity timeline, issues feed, terminal overview, PIN + Face/Touch ID login. - `/m/*` route group with platform-aware redirect from `/`. - `mobile-ios.css` — safe areas, momentum scroll, 16px touch input rule, coarse-pointer hover suppression, dynamic-viewport sizing. - iOS PWA meta tags, web manifest with shortcuts, theme-color per scheme. - `scripts/ios-init.sh` + npm scripts (`ios:init/sync/open/run`) + `docs/IOS.md` covering bootstrap, permissions, push, TestFlight.
…r UX Wraps the React SPA in a Capacitor 7 native shell so Eryxon Flow runs as a real Android APK / AAB on phones and especially tablets (Pixel Tablet, Galaxy Tab S9, Lenovo Tab P12, foldables). The web build stays the source of truth — there is no parallel native tree. Native bridge (src/native/) - Thin abstractions over Capacitor with web fallbacks: haptics, scanner (ML Kit barcode + BarcodeDetector fallback), camera, biometric, network status, app shell (status bar, splash, hardware back). - Platform helpers: isNativeApp / isAndroidNative / isTabletViewport. - Components import from @/native only — never @capacitor/* directly. Mobile shell (src/components/mobile/) - MobileShell wraps every authenticated route: hardware back wired into React Router, persistent OfflineBanner, floating ScanFab on operator routes, body classes (app-mobile / app-android) for CSS scoping. - ScanDialog: native ML Kit on Android, in-dialog camera preview on web, manual entry fallback. Haptic success/error confirms. - ScanFab navigates with ?q=<code>; WorkQueue picks it up and filters. Tablet-first responsive layer (src/styles/mobile.css) - Safe-area utilities for status bar / nav bar / display cutouts. - Touch targets bumped to ≥44 px on the mobile shell only — desktop SPA stays dense. - sw600dp / sw720dp landscape breakpoints for Pixel Tablet / Galaxy Tab. - prefers-reduced-motion honoured (Android system setting). PWA / offline - manifest.webmanifest with shortcuts (Work Queue, Scan, Activity) + maskable icons. - sw.js: network-first for app shell, cache-first for assets, bypass for Supabase. Operators stay functional if shop floor WiFi blips. Android project (android/) - AndroidManifest: resizeableActivity, adjustResize, multi-window, Camera/Biometric/Push/Notifications permissions, deep links (eryxon:// + https://app.eryxon.eu), camera optional so kiosk tablets without rear cameras still install. - Material themes with edge-to-edge dark status/nav bars, values-night colors, network_security_config restricting cleartext to localhost / LAN / .local (self-host friendly). - Adaptive launcher icon with Eryxon brand mark + monochrome variant for Android 13 themed icons. - ProGuard rules for Capacitor + ML Kit + biometric reflection paths. - App-bundle splits (language/density/abi), Java 17, R8 release shrinking. - Gradle daemon tuned for ML Kit's memory footprint. Scripts + docs - npm run android:sync / android:run / android:livereload / android:assemble:debug / android:assemble:release / android:bundle:release. - docs/ANDROID.md covers prerequisites, workflows, plugin map, signing, FCM push setup, and self-hosted server URL via CAPACITOR_SERVER_URL. Validation - npm run build: clean. - npm run test:run: 758 tests pass across 54 files. - npm run lint: net delta vs baseline is -1 error / -1 warning. - npx tsc --noEmit: no type errors in any new file. https://claude.ai/code/session_01JMb7RSgCE6PG3inJx14LQW
… MCP is Live The previous v0.5 refresh overstated the status of several surfaces. Corrected across landing pages, intro pages, feature pages, changelog, and roadmap. Status corrections: - Web app: Beta (was implied "Live today") - REST API: Beta (was implied live) - Webhooks: Beta (was implied live) - MQTT: Beta (was implied live) - FrePPLe planning adapter: Beta (was "production-ready") - Odoo planning adapter: Beta (was "scaffold") - MCP server: Live (correct, kept) - Android (native, offline, fast cold start): Coming Soon (kept) - iOS: Coming Soon (kept) Visual updates: - Hero pill strip: "Web App — Beta" (amber) instead of "Native in Browser" (primary blue) - Platform 4-card row: web app gets a Beta label, integration card now splits "MCP server: Live" from "REST/Webhooks/MQTT: Beta" - Mobile app section badges relabeled "Coming Soon" (was "In Development") to match the user-facing terminology - Current Status cards: added Beta callouts on web app and hosted version cards Content updates: - Scheduling and ERP integration pages: adapter status table now reads "Beta" for both FrePPLe and Odoo, with a note that interfaces may still change and to pilot on non-critical work first - Introduction pages (EN/DE/NL): new status callout pinning Beta vs Live vs Coming Soon for each surface, near the top - Changelog Current Status: explicit Beta-vs-Live breakdown - Roadmap: same Beta-vs-Live breakdown in the May 2026 status note
The MQTT reliability section suggested filtering mqtt_logs by tenant_id and status='failed', but that table has mqtt_publisher_id and a boolean success column instead. Tenant scope lives on mqtt_publishers, so self-hosters need to subquery publishers by tenant first. Replace the prose with an explicit SQL example using the real columns. Found by chatgpt-codex-connector review on PR #580.
- MobileLogin: route to ROUTES.MOBILE.QUEUE after a successful PIN so the operator stays inside the touch shell (bottom tabs + scanner). Going to the desktop /operator/work-queue dropped them out of the mobile chrome. - MobileIssues: align with the real `public.issue_status` enum (pending/approved/rejected/closed). The previous mapping (open/acknowledged/resolved/rejected) meant freshly reported issues default to `pending` and never showed up in the Open tab. The Open tab now bucket-tests `status === 'pending'`; everything else lands in Resolved.
Self-audit of the v0.5 doc refresh, addressing every nit found:
Accuracy:
- Hero description and tagline (EN/DE/NL): no longer claim native
Android/iOS apps are available "today" — they are coming soon, only
the browser app exists today
- Introduction Integration-First section (EN/DE/NL): retry/circuit
breaker/dead-letter logging are MQTT-client features only, not
webhooks; webhooks have HMAC signatures but no retry hardening.
Re-scoped the sentence and added Beta/Live tags inline (REST Beta,
webhooks Beta, MQTT Beta, MCP Live)
- Scheduling page: corrected FrePPLe adapter test count (21, was 20)
and added Odoo's actual count (2) to underscore the narrower coverage
Historical entries:
- Changelog v0.5.1 entry: dropped "final active-development release"
framing on v0.5.0 and v0.5.1 since native mobile development
started after v0.5.1; reframed v0.5.0 as the last web-app feature
release before the native push
Localization polish:
- DE: "gleiche Backend" -> "gleiches Backend" (Backend is neuter)
- NL: smoother phrasing for offline support claim
("operators tijd kunnen blijven registreren" instead of "operators
kunnen blijven tijd schrijven")
CodeRabbit findings on PR #577: - nginx: server-level security headers were silently dropped on locations that defined add_header (per ngx_http_headers_module's no-merge rule). Extracted X-Frame-Options / X-Content-Type-Options / X-XSS-Protection / Referrer-Policy into nginx-security-headers.conf and re-included from every location that defines its own add_header. - vite-plugin-pwa: dropped skipWaiting + clientsClaim and switched registerType to 'prompt'. A new SW now installs and waits; a Sonner toast (PwaUpdatePrompt) shows the operator a Reload action that posts SKIP_WAITING to the SW. No more forced mid-shift reloads. New i18n keys in en/nl/de common.json. - index.html: meta CSP shipped to production stripped of dev-only origins (localhost / 127.0.0.1, http+ws). Done at build time via a small Vite HTML transform so `vite dev` keeps HMR working. - vercel.json: dropped Google Fonts origins (we bundle @fontsource/inter) so the response-header CSP matches the meta CSP and browsers no longer enforce a confusing intersection. Also: registerSW.js is no longer emitted (React hook handles SW registration directly), so the cache rules for /registerSW.js were removed from nginx, vercel and _headers.
The Vercel deploy was failing on `npm ci` because @capgo/capacitor-native-biometric@^7 needs Capacitor 7+ as a peer, while we ship on Capacitor 6. Drop to ^6.0.4 (last v6-compatible release) and regenerate package-lock.json. While I was in there, address the real lint findings in the mobile code: - PullToRefresh: replace `armed.current` with `dragging` state so transition lookups don't read a ref during render. - MobileScanner: hoist launchScan above the auto-launch effect and wrap with useCallback so eslint's no-use-before-define doesn't fire. - MobileQueue / Activity / Issues / Login / OperationDetail / Terminal: defer the initial fetch via queueMicrotask so synchronous setState no longer runs inside useEffect; also drop the redundant setLoading(true) calls (state already starts as true).
Resolved conflicts: - index.html: kept iOS viewport-fit=cover + maximum-scale=1, kept the PWA icon set + apple-touch-icon from #577. Manifest is now generated by vite-plugin-pwa, so the explicit <link rel="manifest"> is no longer needed (vite-pwa injects it). manifest-src 'self' added to CSP. - public/manifest.webmanifest: deleted my hand-written file — the PWA plugin now generates it from vite.config.ts. Folded my mobile-only shortcuts (Queue / Scan / Issues) into the plugin's manifest config. - package*.json: took the merged set; ran npm install to resolve.
Resolved conflicts: - Capacitor 6 → 7 across the board. iOS plugins are bumped to v7 so they share the same peer (@capacitor/core@^7.6.4) as the Android plugins introduced by #579. Bumps @capgo/capacitor-native-biometric back to ^7.6 too (its peer requires Cap 7). - capacitor.config.ts: combined into a single file with both `ios:` and `android:` sections, plus the CAPACITOR_SERVER_URL override path from #579 for live-reload. - src/lib/native → src/native: collapsed to one bridge module. Status-bar / keyboard helpers (iOS) and platform/scanner/biometric/network/camera/ appShell helpers (Android) all live under src/native now. Components import from `@/native`; nothing else imports @capacitor/* directly. - src/native/platform.ts: unified to detect iOS/iPadOS/Android/web with cached fingerprints and exposes shouldUseMobileShell / hasFinePointer. - src/native/biometric.ts: kept the Android verifyBiometric/isBiometric API and added back getBiometricAvailability / verifyIdentity for the iOS login screen. - src/native/scanner.ts: re-introduced ScannerUnavailableError and ScannerPermissionError so the iOS scanner page can route the user to the manual lookup fallback when the bridge isn't available. - src/components/mobile/MobileShell.tsx: merged shells. Bottom tab bar + iPad split (mine) plus hardware-back wiring, OfflineBanner, and the app-mobile / app-android body classes (theirs). - src/main.tsx: kept the data-platform tagging (drives mobile-ios.css) and added initNativeShell() to boot status bar / splash / hardware back. - src/index.css: imports both styles/mobile.css (Android tablet) and styles/mobile-ios.css (iOS) — they don't overlap. - index.html: viewport keeps user-scalable=no for the native shell while the new manifest meta is satisfied by vite-plugin-pwa. - .gitignore: dropped duplicate dev-dist marker; kept the Android + iOS native-build ignores. - Hooks: useHaptics now talks to the unified `haptics` API; useNative exposes both isNativeIOS and isAndroidNative for routing decisions. Validation: - npm run build → clean (PWA SW + 205 precached entries) - npm run test:run → 54 files, 758 tests pass
Six real findings, all from the umbrella merge: 1. App.tsx (P1): drop the App-level <MobileShell> wrap. With the merge it was mounting around AppRoutes, so /admin and /auth routes were getting the bottom tab bar + offline banner + app-mobile body class, and /m routes ended up with a duplicate MobileShell. Mobile chrome now only mounts under MobileRoutes (/m/*). 2. capacitor.config.ts (P1): drop `iosScheme: 'https'` for the bundled case — Capacitor docs explicitly forbid `http`/`https` for the local WebView scheme (WKWebView reserves them) and the iOS app would fail to load the bundled /m/* app at startup. Falls back to the default `capacitor://` scheme; the remote-URL branch keeps using the matching protocol. 3. MobileActivity.tsx (P2): "Today" tile was reading `grouped[0]` which is simply the most recent day with activity, so on the first sign-in of the day operators saw yesterday's hours. Switched to an explicit today-key lookup. 4. MobileTerminal.tsx (P2): mirror the desktop terminal — load `cells` separately and seed the per-cell counters with the full active set, so idle cells render with `0 active / 0 queued` instead of disappearing into the "No cells configured" empty state. Also restored cell-sequence sort to match the desktop ordering. 5. ScanDialog.tsx + native/scanner.ts (P2): plumb an AbortSignal through `scanOnce` so the web-fallback `getUserMedia` stream is torn down the moment the dialog closes — previously the camera indicator stayed on until the 30s scan timeout fired. 6. vite.config.ts (P1, from #577): the prod CSP-strip plugin would also strip `localhost` / `127.0.0.1` from the meta CSP for self-host builds, which breaks the documented Docker setup that uses VITE_SUPABASE_URL=http://127.0.0.1:54321. Skip the strip when the configured Supabase URL is itself local.
… preview Native bridge bugs - platform.ts: stop caching isTabletViewport(). The viewport flips on rotation, iPad Slide Over, and Android freeform — a cached value lied about all of those. Re-evaluate on every call; reactive consumers read via useNative() which already re-snapshots on resize/orientationchange. - network.ts: close the unmount-during-await leak. The Capacitor Network handshake is async (dynamic import + getStatus + addListener); if the component unmounted between awaits the cleanup ran first and the listener registered after. Now we track an `unmounted` flag at every await boundary and tear down a listener that resolves post-unmount. - camera.ts: bitmap.close() in a finally block so cheap Android tablets don't leak GPU/native memory across repeated NCR captures. Also added double-settle guards on the file-input (Safari sometimes fires both cancel and change with no file). - appShell.ts: hardware back now dispatches Escape first to close any open Radix dialog/sheet/popover/dropdown before popping the route. Operators with an OperationDetailModal open no longer get yanked off the page on the first back press. - scanner.ts: fixed the TDZ trap where a pre-aborted signal called cleanup() before timeoutId / raf were declared. Reordered with let declarations + named function for cleanup so cleanup/onAbort can reference each other safely. Mobile shell polish - ScanFab.tsx: unified bottom offset across breakpoints (the bottom nav is rendered at every size, so the lg: override was wrong). Bumped z-index to 50 so the FAB stays above sticky page headers. - MobileShell.tsx: dropped the redundant cn() + min-h-screen wrapper that was duplicating chrome height with operator pages and pushing scroll content under the safe-area inset. <main> already gets min-h-0 flex-1. i18n parity - Added the full mobile.* / filters.* / scanner.* / activity.* / workQueue.* / terminal.* key blocks to en/nl/de translation.json so Dutch and German operators no longer fall through to English fallback strings on every mobile screen. STEP + PDF preview on mobile - MobileOperationDetail.tsx: the file list now lazy-loads PDFViewer (~127 KB gz) and STEPViewer (~520 KB gz) on tap. iPad Pro M-series and modern Android tablets render the 3D viewer fluidly — full shop-floor inspection without walking to the desktop terminal. STEP files are pre-fetched as a Blob to dodge the supabase signed-URL CORS dance inside the Three.js loader. blob: URLs are revoked on close. Tests - src/native/platform.test.ts: 11 new tests covering Capacitor detection, iPhone vs iPad UA + touch-points heuristic, and (most importantly) that isTabletViewport() does NOT cache across rotation. - src/native/scanner.test.ts: 3 new tests covering ScannerUnavailable fallback and pre-aborted-signal handling (no TDZ crash). Docs - docs/IOS.md: new "Compatibility (across all three apps)" section covering min OS, login flow, hosted vs. self-hosted backends, language switching, viewport handling, supported devices, and the lazy 3D/PDF viewer trade. Plus a "Remaining iOS-only gaps" checklist for what has to land in Xcode / Apple Developer before TestFlight (APNs key, Universal Links, splash + app icons, Info.plist localizations). Validation: 772 tests pass (added 14), build clean, PWA emits 205 precached entries.
Build chain / chore
- tsconfig.app.json: 0 TS errors in the mobile + native surface (the 65
pre-existing errors elsewhere are unrelated to this PR).
- vite.config.ts: switch to `manifest: false` so the hand-curated
`public/manifest.webmanifest` is the single source of truth for PWA
install metadata. The plugin no longer overwrites it at build time.
Re-link `<link rel="manifest" href="/manifest.webmanifest">` from
index.html so browsers find it.
- public/icons/: generated `icon-{192,512,maskable-192,maskable-512}.png`
to satisfy the static manifest's icon paths (previously the install
would 404 on icons).
- includeAssets: PWA precache now picks up the static manifest and the
/icons/ set so they're available offline.
PWA install plumbing — end-to-end
- WorkQueue.tsx: the home-screen "Scan Job" shortcut points at
/operator/work-queue?scan=1. Added a handler that pops the in-app
ScanDialog when ?scan=1 is on the URL, then strips the param so a
reload doesn't re-fire it. ?q= is also handled (pre-fills search after
a successful scan).
- ScanDialog rendered directly on the operator work queue (used to be
reachable only via the dead ScanFab).
- ScanFab.tsx: deleted. Was redundant with the bottom-tab "Scan" entry
on the mobile shell and the new `?scan=1` handler — keeping it would
have left a never-mounted component in the tree.
Native bridge
- src/native/push.ts: production-ready APNs / FCM registration helper.
Honors permission state, falls back silently on the web, deduplicates
registration listeners, and times out at 15s so it can't block the
sign-in flow waiting on the network.
- AuthContext.tsx: registerPushNotifications() is now invoked on the
SIGNED_IN auth event so the device token handshake happens on first
authenticated launch. Backend dispatch isn't wired yet (documented as
a remaining gap in IOS.md / ANDROID.md), but the iOS / Android side is
ready end-to-end.
- MobileQueue.tsx: split-view decision now uses `useTabletLayout`
(viewport-based) instead of UA-based `isIPad`. iPad Slide Over /
Android freeform / split-screen now correctly collapse to a
single-column shell, where the previous UA fingerprint lied.
- platform.ts: replaced the type-only Capacitor import with the proper
`typeof CapacitorBridge` typing so `tsc --noEmit` passes cleanly.
Tests
- src/components/mobile/MobileShell.test.tsx: render smoke test —
asserts the bottom tab bar renders on /m/queue and is hidden on the
full-screen /m/scan and /m/login flows. Mocks the platform / theme
context dependencies so it doesn't need a real Supabase environment.
Docs
- docs/ANDROID.md: mirrored the IOS.md compatibility table (min OS,
auth, hosted vs self-hosted, language, viewports, devices) plus a
"Login flow", "PDF + STEP CAD preview", "PWA install — manifest of
record", and "Hardware back / dialog dismissal" sections so the
Android side has the same depth of documentation as iOS.
Validation: 775 tests pass (added 3), build clean, PWA emits 210
precached entries, static manifest ships unmodified to dist/, all four
icon paths the manifest references actually exist.
New top-level reference at docs/DEPLOY_AND_TEST.md covering all three
surfaces end-to-end. Cross-linked from README.
Each surface gets its own section:
1. PWA — local dev, prod build, hosted deploys (Vercel + Cloudflare
wired in CI), how to install on each desktop/mobile platform,
install verification checklist, Lighthouse PWA audit target,
CI pipeline.
2. iOS / iPadOS — Xcode + signing prereqs, the npm run ios:init
bootstrap, simulator + physical-device test matrix, the exact
flows to exercise (cold launch, sign-in, PIN, biometric, scanner,
pull-to-refresh, swipe actions, timer, offline banner, PDF + STEP
preview, push registration, status bar, modal dismissal),
TestFlight upload, App Store submission, links to the remaining
gaps.
3. Android — Studio + SDK + Java 17 prereqs, npm run android:open
bootstrap, recommended emulators (Pixel 6/8 Pro/Tablet, Galaxy Tab,
Z Fold 5), physical-device test matrix with the Android-specific
flows (hardware back through OperationDetailModal, system gesture,
Material You themed icon, split-screen, network security config,
FCM push), AAB build and Play Console rollout.
Plus a cross-cutting test matrix (vitest, tsc, eslint, vite build,
api e2e, capacitor sync, on-device, Lighthouse, SW offline) and a
release-readiness checklist that covers the manifest reconciliation
(static file is the source of truth; vite-pwa runs in `manifest: false`
mode), the four /icons/ PNGs that ship, the SW prompt-mode update
contract, version bumps for native shells, and the `?scan=1` shortcut
handshake.
Closes the deploy / test gap that an operator pretending to ship this
end-to-end would otherwise hit.
…lback Three real architectural bugs from the cross-app audit. None were caught by lint / tsc / tests; the failure modes only show up at runtime. A. Push registration fires on every SIGNED_IN event — including token refresh, browser tab restore, and Supabase replaying the most recent event to a newly mounted listener. Operators on iOS would see the APNs permission sheet re-pop on every cold start, and we'd fire a registration handshake that's idempotent but wasted. Fix in `AuthContext.tsx`: track the last user id we registered for in a closure-scoped variable. Only fire `registerPushNotifications()` when the *user id* changes, not on every SIGNED_IN. Reset the tracker on TOKEN_REFRESHED-failure and SIGNED_OUT. Token refreshes no longer trigger a register call. B. The PWA service worker registers inside the Capacitor WebView too. `useRegisterSW()` calls `navigator.serviceWorker.register()` on mount. On Android (Chrome-based WebView) this would install a SW for the app origin and start intercepting Supabase calls when running with CAPACITOR_SERVER_URL=https://... Fix in `PwaUpdatePrompt.tsx`: split into a thin gate component that bails out via `isNativeApp()` *before* the `useRegisterSW` hook runs (rules of hooks: the hook is in the inner component that's only mounted on the web). iOS's WKWebView ignores SWs entirely so this is defense-in-depth there; on Android it's the difference between a working app and one that gets its API calls hijacked by an old SW. C. There is no `appUrlOpen` handler — magic-link / OAuth callback URLs on iOS / Android can't get the auth tokens back into the WebView. The user taps a magic link in their email, the OS routes it to the app via Universal Link or URL scheme, and the URL was being dropped. Fix in `src/native/appShell.ts`: register a Capacitor App.appUrlOpen listener inside `initNativeShell()`. Pulls `access_token` / `refresh_token` out of the URL fragment and calls `supabase.auth.setSession()` for magic-link + recovery flows; pulls `code` out of the query string and calls `supabase.auth.exchangeCodeForSession()` for the OAuth PKCE flow. Logs unrecognised callbacks to `adb logcat` / Xcode console at warn level instead of crashing. This is the missing piece that makes auth work end-to-end on iOS + Android. The Apple Developer / Google Play side still needs the Universal Link entitlement / app-links verification configured (see docs/IOS.md "Remaining iOS-only gaps") — those are out-of-band per-tenant config, not code. Verifications: - `npm run test:run` — 775 tests pass. - `npm run build` — clean. Main bundle has 0 references to `@capacitor` (the bridge boundary still holds; every plugin is dynamically imported into its own chunk).
Self-hosted, on-prem deployment is the primary path for Eryxon Flow (most shops run their own Supabase + their own LAN host). The deploy guide previously led with App Store / Play Store / TestFlight, which sells SaaS distribution as the canonical workflow when it's actually the rare case. - docs/DEPLOY_AND_TEST.md: new "Self-host install paths at a glance" matrix at the top. Each surface section now leads with the no-store-required self-host path: cable-install via Xcode (free or Ad Hoc) for iOS, `adb install` / hosted APK / MDM push for Android, in-browser PWA install for web. The App Store / Play Store sections are kept but explicitly framed as "only if you ship as SaaS". - docs/DEPLOY_AND_TEST.md: explicit recipes for pointing each native shell at a LAN host — both `VITE_SUPABASE_URL=http://shop-floor.local` baked-in builds and `CAPACITOR_SERVER_URL=http://shop-floor.local` live-reload. - scripts/ios-init.sh: also auto-registers the `eryxon://` URL scheme in `CFBundleURLTypes` so magic-link callbacks work on iOS without needing Universal Links + an Apple App Site Association on a public host (which self-hosters can't provide). The handler in `src/native/appShell.ts` already accepts both `eryxon://auth/...` and `https://app.eryxon.eu/auth/...`. Same script also seeds `CFBundleDevelopmentRegion=en` + `CFBundleLocalizations=[en,nl,de]` so the App Store listing fields are correct on the rare SaaS path. - Android: the `eryxon://` intent-filter was already in `android/app/src/main/AndroidManifest.xml` from the original Android PR — no changes there, just documented.
Eryxon Flow is primarily self-hosted. Operators run their own Supabase + app stack on a plant-internal host (`http://shop-floor.local:8080`, `http://192.168.1.50:54321`, etc.) — usually over plain HTTP because the shop hasn't fronted it with TLS yet. Without these fixes, every surface had at least one block in that path. Four real LAN-blocking gaps. All closed. 1. Meta CSP didn't include the configured Supabase host - vite.config.ts: applyCspBuildRewrites() now injects the origin from `VITE_SUPABASE_URL` (and the matching ws:/wss: variant) into the `connect-src` directive at build time. Verified with three shapes: LAN hostname → http://shop-floor.local:54321 ws://shop-floor.local:54321 LAN IP → http://192.168.1.50:54321 ws://192.168.1.50:54321 Cloud Supabase → https://abc.supabase.co wss://abc.supabase.co - The dev-only localhost strip is preserved when the configured URL itself is local (Docker self-host pattern). - Bug fix on the regex: the negation set excluded single quotes, which ended the match inside `'self'` and meant the injection silently no-op'd. Switched to `[^";]` since the CSP value is wrapped in double quotes. 2. Android `network_security_config.xml` only whitelisted two /24s - Broadened to the full IETF private-use space (RFC 1918): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 — enumerated as individual <domain> entries because Android NSC doesn't support CIDR. Plus link-local (169.254.0.0/16), the conventional LAN suffixes (.local, .lan, .home, .internal, .corp, .private, .intranet), and emulator loopbacks (10.0.2.2, 10.0.3.2). 3. iOS App Transport Security was blocking every plain-HTTP LAN call - scripts/ios-init.sh now patches Info.plist on `npm run ios:init`: - NSAppTransportSecurity.NSAllowsLocalNetworking = true (Apple-blessed escape valve for RFC1918 + .local; doesn't require an App Store review justification). - NSAppTransportSecurity.NSAllowsArbitraryLoadsInWebContent = true so the WebView can load HTTP assets from the LAN host (CSS, fonts, images). - NSBonjourServices = [_http._tcp, _https._tcp] so iOS 14+ resolves shop-floor.local without silently dropping it. - NSLocalNetworkUsageDescription so the OS-level local-network permission sheet shows the right reason. 4. Caddyfile only had a public-domain ACME profile - Rewrote with three named sections: A. Public host via Let's Encrypt (the original — kept, commented). B. LAN host via `tls internal`. Caddy issues a self-signed cert from its own CA so the PWA install prompt fires and the Service Worker can register on `https://shop-floor.local`. Operators install the Caddy root once per device. Driven by $LAN_HOST and $LAN_IP env vars (defaults to shop-floor.local). Adds Service-Worker-Allowed: /, no-cache headers for /sw.js + /env.js, immutable Cache-Control for /assets/*. C. Pure-HTTP LAN (no TLS at all) — explicitly documented as discouraged because the SW won't register, but called out so self-hosters who refuse TLS know native Capacitor builds still work via the network_security_config / ATS exceptions above. Validation: 775 tests pass, build clean, CSP rewrite verified with the three LAN deploy shapes above.
Migrate AI tooling to Codex.
- AGENTS.md: symlink -> regular file (Codex doesn't follow symlinks)
- .agents/README.md: update for Codex
- .agents/skills/: add explore-code, source-command-{explore,graph-status,interrogate}
- .codex/: add 8 subagents (code-explorer, dependency-analyzer, explain-service, find-usages, opentrace, repo-ops, supabase-db, tech-stack) + config.toml + migration report
- src/lib/config.ts: add isSelfHosted() helper + config.test.ts
- src/test/setup.ts: jsdom configurable fix
- .gitignore: broaden .env.* coverage, add .claude/worktrees/, supabase/.branches/
Consolidates AGENTS.md regular-file conversion, .codex/ subagents, src/lib/config.ts isSelfHosted helper, jsdom setup fix, and .gitignore hardening from #581 into this release PR.
… (ERY-34) AuthContext.fetchProfile never selected the onboarding columns, so App.tsx's gate `profile.onboarding_completed === false` evaluated `undefined === false` and no admin was ever routed into the wizard; tenant-level completion was never stamped. - AuthContext: select onboarding_completed + onboarding_step, coerce nullable columns to false/0, add refreshProfile() for re-hydration. - App.tsx: route only incomplete admins into the wizard (needsOnboarding = role === "admin" && onboarding_completed === false); operators are never gated into the admin-only flow. - OnboardingWizard: on complete and skip, stamp tenants.onboarding_completed_at = now() and refreshProfile() before navigating away. Verification: vitest (20 passed, 3 new) + tsc --noEmit clean. Board-approved to land (approval dde8da46). Co-Authored-By: Paperclip <noreply@paperclip.ing>
… (ERY-34) AuthContext.fetchProfile never selected the onboarding columns, so App.tsx's gate `profile.onboarding_completed === false` evaluated `undefined === false` and no admin was ever routed into the wizard; tenant-level completion was never stamped. - AuthContext: select onboarding_completed + onboarding_step, coerce nullable columns to false/0, add refreshProfile() for re-hydration. - App.tsx: route only incomplete admins into the wizard (needsOnboarding = role === "admin" && onboarding_completed === false); operators are never gated into the admin-only flow. - OnboardingWizard: on complete and skip, stamp tenants.onboarding_completed_at = now() and refreshProfile() before navigating away. Verification: vitest (20 passed, 3 new) + tsc --noEmit clean. Board-approved to land (approval dde8da46). Co-Authored-By: Paperclip <noreply@paperclip.ing>
…ersistence (ERY-46) Add the managed-pilot observability baseline so a single request can be traced from edge logs into a durable activity_log row via a shared request_id. - New _shared/observability.ts: resolveRequestId (trust valid inbound x-request-id, else mint), structured edgeLog, shouldPersistPilotEvent filter, and persistPilotEvent writing request_id into activity_log.metadata. - createApiHandler wraps every request: boundary request-id, x-request-id on all responses, request lifecycle logs, automatic edge.error persistence, and a ctx.recordPilotEvent hook for success-path lifecycle events. - errorHandler: extracted mapError as single error->code/status taxonomy; handleError echoes x-request-id. CORS exposes x-request-id. - crud-builder gains opt-in onCreated hook; api-issues records issue.created. - Frontend LogContext extended with the contract fields; docs updated. - Tests: src/lib/__tests__/edgeObservability.test.ts (12) cover request-id propagation and the persistence filter. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-Authored-By: Paperclip <noreply@paperclip.ing>
…ERY-48) Packages the ERY-41 manual restore drill into one engineer-owned command: schema-scoped pg_dump (public/auth/storage) -> storage-volume tar snapshot -> restore into a disposable scratch DB -> extract -> parity + smoke checks. Dump/restore run inside the supabase db container so host pg_dump version drift does not break the drill; read-only checks use host psql. Smoke beyond row counts: sample-file SHA-256 parity, referential integrity (orphan parts/jobs), RLS helper presence (get_user_tenant_id), pilot-tenant shape. Auto-cleans scratch DB + temp on success; --keep/--log flags. - scripts/restore-drill.sh - docs/BACKUP_RESTORE_DRILL.md - package.json: npm run drill:restore Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-Authored-By: Paperclip <noreply@paperclip.ing>
…aths (ERY-51) Turn the ERY-46 observability baseline on across the real pilot paths. - api-job-lifecycle, api-operation-lifecycle: migrate from raw `serve` onto `serveApi`/`createApiHandler`, so each request carries a boundary x-request-id, structured edge logs, and automatic edge.error persistence. Record job.lifecycle / operation.lifecycle / operator.time_entry pilot events via ctx.recordPilotEvent, and forward x-request-id into the webhook-dispatch hop. - webhook-dispatch, mqtt-publish: keep internal-secret auth but resolve the request id at the boundary, echo x-request-id on every response, and persist webhook.dispatch_failed / mqtt.dispatch_failed (per-delivery + top-level) into activity_log with the shared id. - AuthContext: auth.session_recovery and auth.tenant_switch carry the logger contract (eventType + service) on success and failure. - TerminalLogin, MobileLogin: operator.login success/reject/error events. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Co-Authored-By: Paperclip <noreply@paperclip.ing>
….6 (ERY-71) - ML Kit barcode: wire the dead mlkitBarcodeDependencies placeholder into a real com.google.mlkit.vision.DEPENDENCIES manifest meta-data (install-time model delivery); document the air-gapped bundled-model build path in docs/ANDROID.md. - network_security_config.xml: replace the fabricated sample-IP RFC1918 cleartext list (false coverage, no CIDR matching) with a least-privilege strategy — release builds HTTPS-only, dev cleartext confined to debug-overrides, plain-HTTP LAN hosts supported per-host via a documented custom release build or TLS fronting. - Align version surfaces to 0.5.2 (build.gradle + README) with package.json/CHANGELOG. - Add docs/2026-05-24-ery-71-native-packaging-validation.md with the static checks run. Co-Authored-By: Paperclip <noreply@paperclip.ing>
- App.tsx home routing: native-shell admins were gated to /m/queue because the admin branch required !native.isNative. Native admins now land on /admin/dashboard as the comment intends; operators and mobile-shell users still route to /m/queue. - pilot-alert-evaluator: migrate deprecated std@0.168.0 serve() to the Deno.serve() entrypoint (matches other edge functions). The old import fails to start on the current Supabase runtime, breaking the pg_cron job. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3bd21bbb1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .eq("status", "completed") | ||
| .gte("completed_at", startDate) | ||
| .lte("completed_at", endDate); | ||
| .is("deleted_at", null); |
There was a problem hiding this comment.
Restrict completed job count to requested period
get_production_metrics now counts completed jobs without applying start_date/end_date, so the jobs.completed metric returns all-time totals instead of period totals. Any dashboard or automation that compares periods (for example, last 7 days vs last 30 days) will get inflated, non-comparable results even though the response advertises a bounded period.
Useful? React with 👍 / 👎.
| if (event === 'SIGNED_OUT') { | ||
| lastRegisteredUserId = null; |
There was a problem hiding this comment.
Reset push-registration cache when a user signs out
The SIGNED_OUT reset branch is nested inside if (session?.user), but SIGNED_OUT events provide no session user, so lastRegisteredUserId is never cleared on normal sign-out. After signing out and back in as the same user during the same app runtime, push registration is skipped because the stale user id still matches, which can leave notification setup stale after logout/login cycles.
Useful? React with 👍 / 👎.
… privacy
Marketing-layer overhaul on the v0.6 line (EN / NL / DE):
- Localisation/humanizer pass on all marketing copy. The NL was literal AI
translation; rewritten to read like a Dutch metalworker/owner wrote it
("Grip op je orders, van de vloer tot de planning" over "Een rustige,
open-source MES voor jobshops die leveren"). DE rewritten as natural
Hochdeutsch, EN sharpened. Dropped the "calm MES" framing and AI vocab;
kept trade terms (jobshop, MES, snijden/kanten/lassen).
- Roadmap page redesigned from a bare Canny iframe into our own three-column
status board (Shipped / In progress / Planned) on the --ery-* token
contract, with real items and status accents from the semantic state
tokens. Canny is paired in as a "vote on the public board" CTA. Native
iOS / Android sit under "In progress" as in-development work.
- Imprint (/imprint/) + Privacy (/privacy/) pages, design-system-styled via a
shared Legal.astro. Entity data (Sheet Metal Connect e.U., Wien AT) taken
1:1 from vanenkhuizen.com; nothing fabricated. Both carry a prominent
Apache 2.0 "AS IS" / use-at-your-own-risk disclaimer. Linked locale-aware
from the footer.
No version/status bloat on any marketing page (versions stay in the release
notes). Build green; all 15 marketing/legal pages (5 x EN/NL/DE) return 200.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Luke: keep only how-to-use / how-it-works docs, no snapshot garbage. - Delete guides/release-proof-v0-5-1.md (historical proof snapshot) - Remove its sidebar entry from Getting Started - Drop the dangling changelog link to the deleted page - Strip the 'v0.5.1 proof snapshot is historical context' orientation prose from deployment.md and self-hosting.md leads - Tidy 'release proof' wording in articles/index.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Luke: versions belong only in the changelog, not in the docs. Docs explain how the product works and is used, without version-status enumerations. - Remove the 'Where things stand / Huidige status / Stand heute' status blockquotes from introduction (EN/NL/DE) - Remove the standalone 'Current Status' Section and the 'Stable (v0.6) / On the roadmap' status pills from index.mdx (EN/NL/DE) - De-version the 'One Platform, Every Screen' cards and section copy - Drop the 'May 2026 status' version-matrix lead from roadmap.md Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…gn (dexter/legal-pages)
…pshot (dexter/docs-cleanup)
… tighten FOSS framing The README no longer carries a dated 'Project Status' block or a version badge — version and release state belong in the changelog. Reworded the intro to plain FOSS framing (Apache 2.0, self-hosted MES for metalworking job shops) and removed the dead GEMINI.md row from the AI agent table. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45637cd23d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Tool handlers call supabase.from() directly, so they need the raw client, | ||
| // not the UnifiedClient wrapper which only exposes .select()/.insert()/etc. | ||
| const unifiedClient = createClient(config); | ||
| const supabaseClient = (unifiedClient as DirectSupabaseClient).getSupabaseClient(); |
There was a problem hiding this comment.
Preserve tenant scoping when wiring MCP Supabase client
Switching to getSupabaseClient() bypasses the DirectSupabaseClient wrapper that applies TENANT_ID filtering on every query, so tools now execute raw service-role .from(...) calls without automatic tenant constraints. In any deployment with multiple tenant rows (or operators expecting TENANT_ID isolation), this can leak or mutate cross-tenant data because the server now hands unscoped admin access to all tool handlers; keep requests routed through the scoped wrapper (or re-apply equivalent tenant enforcement before execution).
Useful? React with 👍 / 👎.
…o footer - Remove /pricing/ + /[locale]/pricing.astro; nav 'Pricing' now scrolls to inline #pricing on the landing - Drop redundant 'see all pricing' link on the landing - Add LinkedIn + email contact to the footer creator card Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… pages Docs IA was carrying two redundant navigation layers: the left sidebar group tree AND a horizontal secondary tab-bar (SidebarNav) that listed the same 8 group headings. Removed the tab-bar so docs run a single nav source — minimal top nav + left sidebar. The language switcher it hosted moved into the header next to search + theme, keeping locale switching on docs pages. Footer: the Starlight docs footer was suppressed on docs pages and the menu config still held retired contacts. The footer now renders on docs pages with the marketing IA (brand + Apache 2.0, Code & Community, Resources, Contact) and the Contact column is cleaned across en/nl/de — dropped office@vanenkhuizen, the Managed rollout page link, and the vanenkhuizen.com promo; contact is now Luke's LinkedIn + luke@sheetmetalconnect.com, with locale-aware Imprint/Privacy. Legal pages (Legal.astro, EN/NL/DE): redesigned from a flat single card into a calm two-column layout — sticky publisher/controller identity rail beside a readable legal column, hairline rows, mono VAT/Firmenbuch identifiers, and the Apache 2.0 "AS IS" disclaimer as a warning-accented callout. All statutory fields preserved verbatim. Verified: npm run build green (131 pages), tokens:check passes, astro check clean for changed files (4 remaining errors are pre-existing in untouched Hero/Search/ListCard). HTTP 200 on docs + imprint/privacy EN/NL/DE; docs pages show no tab-bar, footer present, no office@vanenkhuizen. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…inline on landing) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f64cbb3a3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| operation_id, | ||
| name, | ||
| sequence: finalSequence, | ||
| status: "pending", |
There was a problem hiding this comment.
Use a valid status value when creating substeps
add_substep now inserts status: "pending", but the substeps table constraint only allows not_started, in_progress, completed, or blocked (see schema in supabase/migrations/20260121175020_remote_schema.sql). This means every call to this tool will fail at insert time with a check-constraint error instead of creating the substep.
Useful? React with 👍 / 👎.
| // Note: jobs table only has status, created_at, updated_at — no dedicated timestamp fields | ||
| const { tool: startJobTool, handler: startJobHandler } = createStatusTransitionTool({ | ||
| tableName: 'jobs', | ||
| toolName: 'start_job', | ||
| description: 'Start a job (changes status to in_progress and tracks start time)', | ||
| description: 'Start a job (changes status to in_progress)', |
There was a problem hiding this comment.
Restore lifecycle timestamp writes for MCP job transitions
The transition configs no longer set lifecycle timestamp fields, so start_job/stop_job/complete_job/resume_job now only mutate status and leave started_at, paused_at, completed_at, and resumed_at unset when jobs are changed via MCP. Those columns still exist in the schema and are used by lifecycle/metrics flows, so MCP-driven job changes will produce incomplete lifecycle data and can be excluded from time-based reporting.
Useful? React with 👍 / 👎.
The /roadmap/ surface (EN/NL/DE) now shows the live public Canny board instead of a hand-maintained, invented list. Read-only: the site pulls from Canny at build time and renders it in the existing design system. Luke manages the board in Canny's own UI; there is no push/sync-back. - src/lib/canny.ts: build-time, server-side-only loader. Reads CANNY_API_KEY from the build env (never the browser bundle). Groups posts into Canny's own statuses (under review / planned / in progress / complete). Never throws — missing key, network, or rate-limit failures fall back to an honest empty state so the build stays green. Admin URLs are rewritten to public visitor URLs. - Roadmap.astro: four status columns from the live board, per-item title + first-line detail + vote/comment counts + category, empty-column and empty-board states, Canny link-out CTA. Shared --ery-* tokens only. - marketing-content.ts: removed all hardcoded roadmap items; keeps only locale chrome (hero, column labels, empty/meta copy, vote CTA). Column statuses now map 1:1 to Canny. CANNY_ROADMAP_URL points at the real public board (eryxon-flow.canny.io/eryxon). - .env.example: CANNY_API_KEY placeholder (real key stays in gitignored .env, server-side only). Verified: build green with key present (live data in dist), no key in dist/, fallback build green without key. Dev server /roadmap/ EN/NL/DE → 200. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…emplate width The container width/centering was scoped to MarketingLayout, so component-level pages (roadmap, imprint/privacy, pricing) rendered full-bleed with no page width. Promoting it to a :global() utility makes every marketing surface inherit the same design-system container. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: af31cb100a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| description: 'Start an operation (changes status to in_progress)', | ||
| newStatus: 'in_progress', | ||
| timestampField: 'started_at', | ||
| additionalFields: { paused_at: null }, | ||
| validTransitions: { | ||
| 'not_started': ['in_progress'], | ||
| 'on_hold': ['in_progress'], |
There was a problem hiding this comment.
Restore MCP operation lifecycle timestamp writes
start_operation now only sets status to in_progress and no longer stamps started_at (and pause_operation similarly no longer stamps paused_at). When operations are transitioned through MCP instead of the dedicated lifecycle endpoints, those records lose lifecycle timing data that downstream time-tracking/metrics flows rely on, so MCP-driven runs become inconsistent with the rest of the system.
Useful? React with 👍 / 👎.
- add_substep inserted status "pending", which substeps_status_check rejects (allows not_started/in_progress/completed/blocked) — every call failed. Use "not_started" for a newly created substep. - Tool handlers call supabase.from() directly on the service-role client, which bypasses RLS. When TENANT_ID is set, route them through a scoped client wrapper that constrains every query to that tenant (no-op when TENANT_ID is unset, i.e. the default single-tenant self-host). Not addressed: Codex P2 on jobs.ts lifecycle timestamps is a false positive — jobs has no started_at/paused_at/completed_at/resumed_at columns. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…sign-out - get_production_metrics counted completed jobs all-time (jobs has no completion timestamp), making period comparisons non-comparable. Bound by updated_at as the completion proxy, matching the operations query. - AuthContext: the SIGNED_OUT reset of lastRegisteredUserId sat inside the `if (session?.user)` branch, but SIGNED_OUT carries no user, so the cache was never cleared — re-login as the same user skipped push registration. Move the reset into the no-user branch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.6 release notes — scope + review triageScope: v0.6 ships the new website, installable PWA, and stability fixes. Native (iOS/Android) is explicitly out of scope and deferred to a later branch (v0.7+). The Codex review triage (all 8 findings):
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 22c93dd41f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| open: issues.filter( | ||
| (issue) => issue.status === "open" || issue.status === "acknowledged", | ||
| ).length, | ||
| resolved: issues.filter( | ||
| (issue) => issue.status === "resolved" || issue.status === "rejected", |
There was a problem hiding this comment.
Align mobile issue status filters with current issue enum
This screen filters and labels issues using open/acknowledged/resolved/rejected, but the app’s generated Supabase types define issue_status as pending/approved/rejected/closed (src/integrations/supabase/types.ts). As a result, newly created pending issues are omitted from the “Open” tab and approved/closed issues are omitted from “Resolved”, so operators see incorrect counts and missing records on the mobile issues page.
Useful? React with 👍 / 👎.
Release v0.6 — stable installable PWA, FOSS (Apache 2.0), website + docs overhaul
Consolidates all v0.6 work from the feature branches into one release branch.
75 commits ahead of
main.Highlights
Licensing / positioning
App / core features (consolidated)
activity_logpersistence (observability baseline).PWA
Website + docs
/pricingpage with the product's inline pricing + managed-rollout copy (EN / NL / DE).noopservice broke<img>underastro dev).CI / release
Not in this release (roadmap / WIP)
Notes for merge
maincurrently has 2 commits not onv0.6(the ERY-107 re-drill workflow and an ERY-79 article-surface variant). These produce add/add conflicts in:.github/workflows/seed-provisioning-drill.ymlwebsite/src/content/docs/articles/_template.mdwebsite/src/content/docs/articles/index.mdThese need a manual resolve at merge time — the underlying work exists on both sides.
🤖 Generated with Claude Code