Skip to content

Information architecture & sitemap

S3 Worker-proxy path deferred per ADR-005 (Tailscale-only Phase I). Worker-proxy design preserved in docs/design/60-parking/cloudflare-tunnel-from-jetson.md.

Status: draft Author: Agent C (frontend bucket, round 1) Reviewers: Agent B (integration spec), Sophia Mann, Andrew Kent; round-2 critique agent Last updated: 2026-05-10

Today lbzfai.com is a single Astro page with Auth0 SPA login that, post-login, does nothing. Andrew Kent’s 2026-05-10 directive (“all the nested pages users will see on the platform”) requires planning the full page graph before we start adding routes ad-hoc. The v1.9 plan stipulates a Flask/FastAPI dashboard on :5000 running on the Jetson, accessed via Tailscale — but it does not say where lbzfai.com fits in relation to that dashboard, or what lives where. This doc resolves that.

Anchor artifacts:

  • ~/lbzfai/src/pages/index.astro (the current one and only page)
  • ~/lbzfai/CLAUDE.md §“Architecture” (Astro static + client-side Auth0 + Cloudflare Workers w/ Static Assets)
  • docs/technical/architecture.md (Jetson dashboard on :5000, Tailscale for remote)
  • docs/technical/deployment.md (Tailscale-or-bust ops model)
  • docs/factory/current-state.md (the whiteboard + Excel loop the dashboard replaces)
  • A complete page graph for the platform — every URL a user can land on, every nav edge between them.
  • An explicit, defended answer to: does lbzfai.com host the dashboard, or does it redirect authenticated users to the Jetson? This is the most consequential frontend decision in the project.
  • Route names that survive into Phase II/III (4 modules, ~20 cameras, future supervisors).
  • A nav graph that doesn’t require a real router today — i.e., the IA is consistent with output: 'static' Astro plus Auth0 SPA gating for as long as Phase I needs.
  • Spanish-first URL semantics where it doesn’t hurt (e.g., /modulos/angela reads better to Ronald than /modules/angela).
  • The visual design of each page. That lives in the per-persona UX docs.
  • The backend data contract that powers each page. That’s Agent B’s territory.
  • The marketing/landing copy for the public lbzfai.com home page.
  • Mobile-app native shells. Out of scope through Phase III.

The render-location question (most important)

Section titled “The render-location question (most important)”

Question. Where does Ronald’s dashboard actually render — on lbzfai.com pulling Jetson data through Cloudflare, or directly on the Jetson via Tailscale on http://<jetson-tailscale-ip>:5000?

Three viable architectures:

OptionWhat it isProsCons
A. Tailscale-directlbzfai.com is only marketing + login. Authenticated users see a “Open dashboard” link that points to http://<jetson-tailscale-ip>:5000. Tailscale must be installed on every user’s device.Simplest. Matches v1.9 plan literally. No data leaves the Jetson. No new cloud infra needed.Mariana cannot use it from her phone unless she installs Tailscale. Public URL bar shows raw IP. Cannot SSO across the two surfaces easily. Hard to share a screenshot or a link with anyone.
B. Cloudflare-proxiedlbzfai.com hosts every page. A Cloudflare Worker proxies a narrow JSON API to the Jetson over Tailscale (the Worker is a Tailscale node). Live video is either tunneled or replaced with periodic stills.One URL for everyone. Mariana on her phone needs nothing installed. Auth0 covers everything. We control the public surface.Real infra to maintain (Worker-as-Tailscale-node is non-trivial). Data leaves the Jetson. Latency budget tightens. New attack surface.
C. Hybridlbzfai.com hosts the executive dashboard (Mariana — slow-moving aggregate data, no live video). Ronald’s engineer dashboard stays on http://<jetson>:5000 via Tailscale.Pragmatic: Mariana never needs Tailscale; Ronald’s working dashboard stays low-latency and local.Two codebases, two auth surfaces (potentially), users hit different URLs depending on role. Documentation overhead.

Proposed answer (Frontend bucket recommendation): Option C — Hybrid. Justification:

  1. Mariana on her phone is the hardest constraint. She will not install Tailscale on personal devices for a family-business product (assumption — see open question). Option A breaks her. Option B works but introduces a Tailscale-aware Cloudflare Worker — a significant new piece of infra that has to be production-grade by July 2026.
  2. Ronald is already at a desk inside the plant. His latency budget is sub-second. Direct Jetson access over local LAN (when in-plant) or Tailscale (when remote) is the right answer for him. Putting his dashboard behind a public URL adds latency and risk for zero user benefit.
  3. The hybrid contains the blast radius of either failure mode. If the Cloudflare Worker breaks, Mariana’s view degrades to “see Ronald’s last shared screenshot” — Ronald keeps working. If Tailscale breaks, Ronald loses his dashboard but Mariana’s executive view still works on cached data.
  4. It matches the v1.9 plan. The v1.9 plan already names a Flask dashboard on :5000 accessed via Tailscale. Option C keeps that intact and adds an executive surface on top.

Owner of the final call: Agent B (integration spec). This doc proposes the answer; Agent B’s spec must accept, reject, or revise. If Agent B rejects, every section below shifts — see [What’s weak in this doc] §1.

lbzfai.com (PUBLIC + AUTHENTICATED — Cloudflare Workers static)
├── / [public] Marketing landing. Current page, lightly refactored.
├── /entrar [public] Auth0 login. Or stays at "/" with login button — see below.
├── /salir [auth] Auth0 logout target.
├── /app [auth] Application shell. Role-aware: redirect based on Auth0 role to /app/ejecutivo or /app/ingeniero.
│ │
│ ├── /app/ejecutivo [executive, admin] Mariana's home dashboard.
│ │ ├── /app/ejecutivo/modulo/angela [executive, admin] Per-module executive view.
│ │ ├── /app/ejecutivo/modulo/carmenza [executive, admin] Phase III.
│ │ ├── /app/ejecutivo/modulo/camisetas [executive, admin] Phase III.
│ │ ├── /app/ejecutivo/modulo/empaque [executive, admin] Phase III.
│ │ └── /app/ejecutivo/historico [executive, admin] Trend / monthly summary.
│ │
│ ├── /app/ingeniero [engineer, admin] "Go to the on-plant dashboard" landing + status panel.
│ │ ├── /app/ingeniero/abrir-jetson [engineer, admin] Smart redirect: tries http://<jetson-tailscale-ip>:5000;
│ │ │ falls back to instructions if user is not on Tailscale.
│ │ └── /app/ingeniero/exportar [engineer, admin] Excel export trigger UI (the export job itself runs on the Jetson).
│ │
│ ├── /app/investigacion [research, admin] ITBA view. Read-only aggregate; see auth0-roles-and-access.md.
│ │
│ ├── /app/admin [admin] Sophia/Andrew. Role management, audit, deploy status.
│ │
│ └── /app/cuenta [any auth] User account: language preference, sign out.
└── /soporte [public] Static support / contact page. EN+ES.
http://<jetson-tailscale-ip>:5000 (AUTHENTICATED via Tailscale + a separate password or
a Cloudflare Tunnel that brokers Auth0 — see below)
├── / Ronald's home dashboard (the most important screen — see ronald-dashboard-ux.md).
├── /modulo/<name> Per-module live view (Angela in Phase I).
├── /modulo/<name>/puesto/<id> Per-workstation view: live video, cycle-time history, current operator (if shown).
├── /modulo/<name>/historico Per-module historical view: hours/days/weeks.
├── /modulo/<name>/exportar Excel export trigger; produces `INDICADORES`-format .xlsx.
├── /tiempos-perdidos Lost-time entry / coding (C001–C022). Replaces the manual end-of-day step.
├── /camaras Camera health: feed up/down, last frame, model version, fps.
├── /admin Settings: thresholds (`umbrales`), standard times, station↔operation mapping.
├── /logs Live tail of journalctl-equivalent in browser.
└── /salud Health page for monitoring (no auth needed if Tailscale-gated).
┌───────────────────────┐
│ lbzfai.com / │ public marketing landing
│ (current page) │
└──────────┬────────────┘
│ Auth0 login
┌───────────────────────┐
│ /app │ role-aware redirect
└──────┬────────┬───────┘
executive│ │engineer / admin
▼ ▼
┌─────────────────┐ ┌──────────────────────┐
│ /app/ejecutivo │ │ /app/ingeniero │
│ Mariana home │ │ Ronald landing │
│ ─ module tiles │ │ ─ "Open dashboard" │
│ ─ historico │ │ ─ Excel export │
└─────────────────┘ └──────────┬───────────┘
│ smart-redirect on Tailscale
┌───────────────────────────────┐
│ http://<jetson>:5000 │
│ Ronald's working dashboard │
└───────────────────────────────┘
(parallel)
┌──────────────────────┐
│ /app/investigacion │ ITBA view (read-only aggregate, no live video)
└──────────────────────┘
┌──────────────────────┐
│ /app/admin │ Sophia/Andrew
└──────────────────────┘
  • Public + executive surfaces: Spanish. /entrar, /salir, /app/ejecutivo, /modulo/angela, /historico, /exportar. URLs are user-readable for Mariana.
  • Admin surfaces: English. /app/admin. Internal-only.
  • Jetson surfaces: Spanish. /modulo/<name>, /puesto/<id>, /tiempos-perdidos, /camaras, /admin. Ronald reads these.
  • Plurality: Spanish noun plurals — /modulos for index, /modulo/<name> for one. Match INDICADORES-style vocabulary: puesto, modulo, operario, historico, eficiencia, umbrales.

Page-by-page intent (minimum spec, full UX in per-persona docs)

Section titled “Page-by-page intent (minimum spec, full UX in per-persona docs)”
RouteAudiencePrimary purposeAuth gate
/public + LBZF stakeholdersMarketing landing; “Sign in” CTAnone for view; click → Auth0
/appany authenticatedRole-aware redirect dispatcherrequired
/app/ejecutivoexecutive, adminMariana’s home: 4 module tiles + headline KPIrequired + role
/app/ejecutivo/modulo/<name>executive, adminPer-module executive drill-down (still aggregate, no per-operator detail)required + role
/app/ejecutivo/historicoexecutive, adminTrend view: efficiency by day, week, monthrequired + role
/app/ingenieroengineer, adminRonald’s lbzfai.com home: “Open dashboard” + Excel + Tailscale instructionsrequired + role
/app/ingeniero/abrir-jetsonengineer, adminSmart redirect to Jetson local IPrequired + role
/app/ingeniero/exportarengineer, adminExcel export trigger; calls Jetson; downloads filerequired + role
/app/investigacionresearch, adminITBA read-only aggregate viewrequired + role
/app/adminadmin onlySophia/Andrew: roles, audit log, deploy statusrequired + role
/app/cuentaany authenticatedAccount: language, sign out, support contactrequired
/soportepublicStatic contact / support infonone
RouteAudiencePrimary purpose
/engineer, adminRonald’s home (see ronald-dashboard-ux.md)
/modulo/<name>engineer, adminModule live view: workstation grid + module-level pace
/modulo/<name>/puesto/<id>engineer, adminPer-station: live video, cycle-time history, last 10 cycles
/modulo/<name>/historicoengineer, adminHistorical trend at the module level
/modulo/<name>/exportarengineer, adminExcel export endpoint — produces INDICADORES-format .xlsx
/tiempos-perdidosengineer, adminC001–C022 lost-time coding
/camarasengineer, adminCamera health
/adminadminThresholds, standard times, station↔operation mapping
/logsadminLive log tail
/salud(none — health endpoint)Monitoring health check, no auth

Open question: Does the Jetson :5000 dashboard re-authenticate against Auth0, or rely solely on Tailscale + a shared password?

Three options:

  1. Tailscale-only. If you’re on Tailscale, you’re in. Simplest. Loses per-user role enforcement on the Jetson — every Tailscale user has the same access.
  2. Auth0 on the Jetson too. Flask app integrates Auth0 OIDC. Needs DNS or a static URL the Auth0 tenant accepts as a callback — hard with http://<tailscale-ip>:5000. Possibly possible with tailnet.<ts.net> hostnames.
  3. Cloudflare Tunnel + Auth0 in front of the Jetson. Cloudflare Tunnel terminates a public hostname (jetson.lbzfai.com) onto the Jetson, with Cloudflare Access enforcing Auth0. Eliminates the Tailscale dependency for legitimate users; the Jetson keeps its private IP. This is the strongest production answer if Cloudflare Tunnel proves acceptable — but it does mean LBZF data egresses through Cloudflare’s edge.

Proposed: Start with #1 for Phase I (Tailscale-only on Jetson) because it’s the v1.9 default and works for Sophia/Ronald immediately. Re-evaluate #3 for Phase II once the user count grows past 3 and audit logging becomes a requirement.

Owner of the call: Agent B + Sophia. This doc proposes #1; Agent B’s spec confirms or pushes to #3.

Current page is generically captioned “Production Floor, in Real Time”. Refactor minimally for Phase I:

  • Keep the hero, the eyebrow (“Louis Barton Zona Franca”), the lede.
  • Add a small banner under the hero (Spanish): “Sistema interno de monitoreo de producción. Acceso autorizado únicamente.”
  • The Sign in button now redirects post-login to /app (the dispatcher).
  • After login, the home page either (a) auto-redirects to /app or (b) shows a “Open dashboard” button. Prefer (a).
  • No changes to the deployment story (output: 'static', Auth0 SPA hardcoded fallback, Cloudflare Workers with static assets).

Phase II / III page additions (sketch only)

Section titled “Phase II / III page additions (sketch only)”
  • /app/ejecutivo/modulo/<name> becomes a stack — one route per of the 4 modules.
  • /app/operario/<id> — per-operator screen (only if operators ever log in, which they may not). Labor-law fence: see Agent E.
  • /app/comparar — module-vs-module benchmarking.
  • ITBA twin gets its own deployment with the same routes — see auth0-roles-and-access.md on tenant separation.
  • Option A — Tailscale-direct everywhere. Rejected: breaks Mariana on her phone. See render-location table.
  • Option B — Cloudflare-proxied everything. Rejected for Phase I: significant new infra (Cloudflare Worker as Tailscale node, video proxying). Re-consider for Phase II.
  • One single SPA at lbzfai.com that handles every page. Rejected: kills the static-output deploy story; introduces a JS bundle Astro doesn’t need; harder to gate marketing landing from auth.
  • One Astro multi-page app + no Jetson dashboard at all (everything in Cloudflare). Rejected: the v1.9 plan and the data-locality story require keeping the operational dashboard local to the floor.
  • English URL paths everywhere. Rejected for user-facing routes: Mariana and Ronald should be able to read the URL bar. Admin routes stay English.
  • OPEN: Render-location decision (Option A / B / C). Owner: Agent B, blocking: Phase I deploy, target: end of round-2 critique. This doc proposes C.
  • CLOSED via ADR-005: Jetson auth model. Locked to Tailscale-only for Phase I. Auth0-on-Jetson and the outbound-tunnel proxy are parked in 60-parking/cloudflare-tunnel-from-jetson.md.
  • OPEN: Does Mariana install Tailscale, yes or no? Owner: Sophia + Mariana (via Armando), blocking: Option C viability, target: 2026-05-20.
  • OPEN: Is the Excel export executed on the Jetson and downloaded through lbzfai.com, or only available from the Jetson dashboard? Owner: Agent B, blocking: Ronald’s UX flow, target: 2026-05-20. Current proposal: export endpoint on Jetson; lbzfai.com exportar page proxies the download.
  • OPEN: Should / auto-redirect post-login users to /app, or land them on / with a banner? Owner: Sophia, blocking: nothing critical, target: 2026-05-13.
  • OPEN: Multi-tenant URL convention. If this becomes a SaaS, do tenants live at <tenant>.lbzfai.com, lbzfai.com/<tenant>/..., or separate domains? Owner: Agent E (tenancy posture); see lbzfai-evolution-plan.md.
  • From backend/integration (Agent B): the render-location decision; the Excel-export endpoint contract; the JSON API surface for /app/ejecutivo/* if Option C requires Cloudflare to call the Jetson.
  • From hardware/ML (Agent A, D): confirmation that per-workstation views are 1:1 with cameras (no overlapping FOV that would require a different page model).
  • From business/cross-cutting (Agent E): Colombian labor-law constraints on what data ITBA can see → shapes /app/investigacion.
  • From hardware (Agent A): Tailscale hostname/IP availability for the Jetson; this doc assumes a stable address.
  1. Option C is a recommendation, not a decided architecture. If Agent B picks B (Cloudflare-proxied everything), the whole lbzfai.com vs Jetson split collapses into one set of routes. The sitemap above survives more or less intact, but the data flow underneath changes substantially.
  2. The Jetson :5000 routes are aspirational. No code exists yet. We do not actually know how Flask/FastAPI handles auth on a Tailscale-bound IP without a real domain.
  3. No latency budget per page. “Ronald glances at the dashboard” implies < 1s. We have not measured what that means in practice for any of the proposed pages.
  4. Per-station live-video proposed without a faces-vs-boxes decision. The /puesto/ page assumes a video feed is shown; that may be flatly illegal under Colombian labor law or unacceptable to operators. See mariana-dashboard-ux.md open question; this doc inherits the same risk.
  5. No accessibility analysis. We have not considered screen readers, color contrast for red/yellow/green efficiency badges, keyboard nav. Ronald is presumably sighted, but we don’t know his vision (and the population of future supervisors will vary).
  6. Browser support unspecified. Mariana’s phone could be a 2019 Android with an outdated Chrome — the IA assumes modern evergreen browsers without saying so.
  • Argentina demo (2026-05-13): show the sitemap diagram to Andrew and ITBA. Get sign-off on Option C (or pivot).
  • By 2026-05-20: lbzfai.com page added: /app/ingeniero (placeholder), /app/ejecutivo (placeholder, 4 module tiles, mocked data). /app dispatcher routes by Auth0 role.
  • By 2026-06-15: Jetson :5000/ and :5000/modulo/angela working against synthetic data.
  • By July 2026 deploy: full Option C operating against real Angela camera data.
  • Phase II: add /app/ejecutivo/modulo/<name> for Carmenza/Camisetas/Empaque; consider Cloudflare Tunnel migration.
  • Phase III: scale; re-evaluate Option B.
  • ~/lbzfai/src/pages/index.astro
  • ~/lbzfai/CLAUDE.md §“Architecture”
  • docs/technical/architecture.md
  • docs/technical/deployment.md
  • v1.9 plan: Full Hardware Rationale and Initial Checklist.docx (Drive 10dE-XQZZb5h1ST7v6dkttxMPjWREluuP)