Documentation

PlugOne Master Ownership Manual

Complete A-to-Z owner reference: operate, troubleshoot, connect, secure, back up, tear down, and rebuild.

DashboardHome

1. Complete System Overview

PlugOne is the platform at plugone.us. It hosts the Business Visibility Center (BVC) — an operator console for managing business identity, visibility, and verifiable facts.

Core concepts

  • Entity — a business under PlugOne control (slug + display name + status).
  • Dossier — the per-entity page (codex entries + history).
  • Codex entry — a versioned fact about an entity (key + JSON value).
  • Command — every state-changing action; runs through a single server pipeline and is logged.
  • Command log — append-only audit of every command attempt (success or failure).
  • Roles — admin, moderator (operator), viewer. First signup auto-promoted to admin.
  • QR / registry — public dossier surface (Phase 2; the dossier slug is the route key).
  • PlugStack shell — the BVC chrome at /bvc with left-nav modules.

Public vs private surfaces

  • Public: /, /help, /operators-manual.
  • Auth-required: /bvc, /bvc/entities, /bvc/entities/$id, /bvc/commands, /admin, /dashboard.
  • Anonymous: /auth (signup, sign-in, reset).

2. Full Route Map

PathPurposeRoleComponentTablesFailure
/Landing pagePublicsrc/routes/index.tsxRender error → check root route
/authSign in / sign up / resetPublicsrc/routes/auth.tsxauth.users, profiles, user_roles (via trigger)Email rate limit, invalid creds
/helpHelp & quick linksPublicsrc/routes/help.tsx
/operators-manualThis manualPublicsrc/routes/operators-manual.tsx
/dashboardHigh-level statusAuthedsrc/routes/dashboard.tsxentitiesBlank if no entities
/adminAdmin tools / dossier editAdminsrc/routes/admin.tsxentities, codex_entriesRLS denial if not admin
/bvcBVC shell (redirects to /bvc/entities)Authedsrc/routes/bvc.tsxRedirect to /auth if no session
/bvc/entitiesEntity registry + create formOperator+src/routes/bvc.entities.tsxentitiesDuplicate slug, validation
/bvc/entities/$idDossier (codex + history)Operator+src/routes/bvc.entities.$id.tsxentities, codex_entries404 on bad id
/bvc/commandsCommand log viewerAdminsrc/routes/bvc.commands.tsxcommand_logEmpty if admin RLS not granted

All /bvc/* routes use a beforeLoad gate that redirects to /auth?redirect=<path> if no session.

3. Full Database Map

entities

One row per business under management.

ColumnTypeRequiredNotes
iduuidyesPK, default gen_random_uuid()
slugtextyesURL-safe key, lowercase a–z 0–9 -, unique recommended
display_nametextyesHuman-readable name
statustextyesdefault 'active'
created_byuuidnoauth user id
created_attimestamptzyesdefault now()
updated_attimestamptzyesdefault now()

RLS: SELECT/INSERT/UPDATE allowed for admin or operator. DELETE not allowed.

codex_entries

Versioned facts about an entity. New entry for the same key supersedes the previous one.

ColumnTypeRequiredNotes
iduuidyesPK
entity_iduuidyesReferences entities.id (logical FK)
keytextyese.g. 'name', 'address', 'visibility'
valuejsonbyesUp to 32KB; arbitrary JSON
versionintyesAuto-increments per (entity_id,key)
superseded_byuuidnoSet when a newer version exists
created_byuuidnoauth user id
created_attimestamptzyesdefault now()

RLS: SELECT/INSERT for admin or operator. UPDATE/DELETE not allowed (versioning by insert).

command_log

Append-only audit of every command pipeline call.

ColumnTypeRequiredNotes
iduuidyesPK
actor_iduuidnoWho ran the command
entity_iduuidnoAffected entity if any
commandtextyese.g. 'entity.create'
payloadjsonbyesCommand input
resultjsonbyesCommand output
okboolyesdefault true
created_attimestamptzyesdefault now()

RLS: SELECT admin only. Inserts done by service role from server fn.

profiles

ColumnTypeRequiredNotes
iduuidyes= auth.users.id
emailtextnoSet by handle_new_user trigger
display_nametextnoOptional
created_attimestamptzyesdefault now()

RLS: SELECT/UPDATE own row; admin reads all.

user_roles

ColumnTypeRequiredNotes
iduuidyesPK
user_iduuidyesauth user id
roleapp_roleyesenum: admin | moderator | viewer (operator alias used in code)
created_attimestamptzyesdefault now()

RLS: read own; admin manages all. Used by has_role(uid, role) security-definer function in every other table's policies.

Example rows

entities:
  { id: "...", slug: "thedonuthouse", display_name: "The Donut House", status: "active" }

codex_entries:
  { entity_id: "...", key: "address", value: {"street":"123 Main"}, version: 1 }
  { entity_id: "...", key: "address", value: {"street":"456 Oak"}, version: 2, superseded_by: null }

command_log:
  { command: "entity.create", payload: {slug:"x"}, result: {id:"..."}, ok: true }

4. Authentication System

Flows

  • Signup: /auth → email + password → trigger handle_new_user creates profile + role (admin if first user, viewer otherwise) → session returned (auto-confirm on) → redirect to /bvc/entities or ?redirect.
  • Sign in: email + password → session → redirect.
  • Reset: /auth (Forgot password) → email link to /auth → new password.
  • Email confirmation: Currently auto-confirm = true. Disable in Cloud → Auth → Email if you want manual verification (requires SMTP).
  • Session persistence: localStorage via Supabase browser client; restored on every page load.
  • Redirect logic: /auth accepts ?redirect=/safe/path; only relative paths allowed.

Recovering a locked-out admin

  1. Try password reset at /auth (Forgot password).
  2. If the reset email never arrives, open Cloud → Auth → Users, find the user, click "Send password recovery".
  3. If the role row is missing, run a one-time migration:
    INSERT INTO public.user_roles (user_id, role)
    VALUES ('<auth-user-id>', 'admin')
    ON CONFLICT DO NOTHING;
  4. If completely locked out, create a new user via Cloud → Auth → Users → Add user, then promote them with the SQL above.

5. Permission / Role System

RoleReadWriteNotes
adminEverything (entities, codex, command_log, profiles, roles)Everything; manages rolesFirst signup auto-promoted
moderator (operator)entities, codex_entriesInsert/update entities; insert codexDay-to-day operator
viewerOwn profile + own role onlyNoneDefault for non-first signups
entity ownerImplicit via created_byInherits operator/admin write rightsNo separate ACL row yet

Ownership inheritance

Today, ownership is defined by entities.created_by. Operators and admins can edit any entity. To restrict, tighten the RLS policy to created_by = auth.uid() OR has_role(auth.uid(),'admin').

Transferring ownership

-- Admin SQL
UPDATE public.entities SET created_by = '<new-owner-uid>' WHERE id = '<entity-id>';

Preventing unauthorized access

  • Never bypass the runCommand server fn from the client.
  • Never expose SUPABASE_SERVICE_ROLE_KEY in browser code.
  • Audit /bvc/commands periodically for unexpected actors.

6. Entity + Dossier Workflow

  1. Create entity: /bvc/entities → enter slug (lowercase a–z 0–9 -) and display name → Run command → routes to dossier.
  2. Add codex entries: on the dossier, enter key + JSON value. Each key is versioned automatically.
  3. Publish: add a visibility codex entry: {"public": true}.
  4. Verify: confirm new row appears with version incremented and previous row's superseded_by populated.
  5. QR / registry: the entity slug is the routing key for the future public dossier URL (Phase 2).
  6. Failure recovery: if create fails, the form's red text names the field; for slug collision, choose a unique slug.

Slug rules: 1–64 chars, only a–z 0–9 -. No spaces, no uppercase.

7. Command System

Every mutation flows through runCommand at src/server/commands.functions.ts. Validation runs first; the result and the failure (if any) are written to command_log.

entity.create

  • Payload: { slug: string, display_name: string }
  • Validation: slug 1–64 lowercase a–z 0–9 -, name 1–200 chars.
  • Success: { ok: true, entity_id, result: { id, slug, display_name, status } }
  • Failure: { ok: false, result: { error: "..." } } (e.g. duplicate slug, RLS denial)
  • Logged: actor_id, entity_id (on success), payload, result, ok.
// example
runCommand({ data: { command: "entity.create",
  payload: { slug: "thedonuthouse", display_name: "The Donut House" } } })

codex.add

  • Payload: { entity_id: uuid, key: string, value: any }
  • Validation: key 1–128, value JSON ≤ 32KB.
  • Behavior: inserts new row with version = previous + 1, updates previous row's superseded_by.
  • Success: returns the inserted row.
  • Failure: { ok: false, result: { error: "..." } }
runCommand({ data: { command: "codex.add",
  payload: { entity_id: "<uuid>", key: "address",
             value: { street: "123 Main", city: "Springfield" } } } })

Unknown commands

Return { ok: false, result: { error: "Unknown command: ..." } } and are still logged.

8. Edge Functions / Server Actions

runCommand

  • File: src/server/commands.functions.ts
  • Type: TanStack createServerFn (POST), wrapped by requireSupabaseAuth middleware.
  • Auth: bearer token from session; rejected with 401 otherwise.
  • Input: { command: string, payload: object }
  • Output: { ok: boolean, entity_id: uuid|null, result: object }
  • Tables: entities, codex_entries, command_log.
  • Errors: "Unauthorized" (no session), Zod validation errors, RLS denial in result.error.
  • Debug: check browser network tab for the POST body and response; cross-reference /bvc/commands for the logged row.

dispatch

src/server/commands.server.ts — pure dispatcher; add a new command by registering a handler in the switch and a Zod schema in commands.functions.ts.

requireSupabaseAuth middleware

src/integrations/supabase/auth-middleware.ts — validates JWT, attaches { supabase, userId, claims } to context.

supabaseAdmin

src/integrations/supabase/client.server.ts — service-role client used only by the command-log insert. Never imported from client code.

9. Environment Variables

NameWherePurposeIf missing
VITE_SUPABASE_URLBrowser + .envCloud project URL for browser clientAuth + DB calls fail
VITE_SUPABASE_PUBLISHABLE_KEYBrowser + .envAnon key for browser clientAuth + DB calls fail
VITE_SUPABASE_PROJECT_IDBrowser + .envProject ref idSome helpers misroute
SUPABASE_URLServer-onlyURL for server clientServer fns fail to init
SUPABASE_PUBLISHABLE_KEYServer-onlyAnon key for server-side authed clientrequireSupabaseAuth fails
SUPABASE_SERVICE_ROLE_KEYServer-only secretBypass-RLS admin client (command_log insert)Command log writes fail
LOVABLE_API_KEYServer-only secretLovable AI Gateway accessAI calls fail (none currently)
SUPABASE_DB_URLServer-onlyDirect Postgres URL (admin/migrations)Direct DB scripts fail

Configured in: Lovable Cloud → Secrets (server) and the auto-generated .env (browser). Never edit .env by hand; never expose SUPABASE_SERVICE_ROLE_KEY in browser code.

Rotating safely

  1. For anon/publishable keys: Cloud → API Keys → Rotate; .env updates automatically; redeploy.
  2. For service role: Cloud → API Keys → Rotate service role; update the secret; redeploy server fns.
  3. For LOVABLE_API_KEY: use the dedicated rotate tool (do not delete + add).

10. Cloud (Supabase) Control Manual

  • Auth settings: Cloud → Users → Auth Settings (gear). Toggle email confirmation, HIBP, providers.
  • Tables: Cloud → Database → Tables.
  • RLS policies: Cloud → Database → Tables → row "Auth Policies".
  • Edge Functions: Cloud → Edge Functions (none deployed today; PlugOne uses TanStack server fns instead).
  • Logs: Cloud → Logs → Postgres / Auth / Edge.
  • Storage: Cloud → Storage → Buckets (none today).
  • Inspect failed inserts: Logs → Postgres → filter by ERROR; cross-check command_log row with ok=false.
  • Inspect auth users: Cloud → Users.
  • Inspect command logs: open /bvc/commands as admin, or query SELECT * FROM command_log ORDER BY created_at DESC LIMIT 100.
  • Common repairs: missing role row → see §4; RLS denial → confirm has_role matches; trigger missing → re-run migrations.

11. Cloudflare / Domain Connection

plugone.us is connected via Lovable's custom domain feature.

DNS records

A    @    185.158.133.1
A    www  185.158.133.1
TXT  _lovable  lovable_verify=<token from Lovable>
  • SSL: provisioned automatically by Lovable (Let's Encrypt). No action required.
  • Redirects: set Primary domain in Project Settings → Domains; the other redirects to it.
  • Subdomains: type the full subdomain in the connect dialog; add its own A or CNAME.
  • Cloudflare proxy: if proxied, enable "Domain uses Cloudflare" in Advanced (uses CNAME verification).
  • Caching: keep default; do not cache HTML aggressively or auth flows break.
  • Do not change: A records pointing to 185.158.133.1, the _lovable TXT record, the Primary designation.

12. Deployment / Redeployment

  • Frontend changes: click Publish → Update in Lovable to push to plugone.us. Preview URL updates automatically on every save.
  • Backend changes (server fns, migrations, edge fns): deploy automatically — no manual step.
  • Test before deploy: use the Preview URL (id-preview--*.lovable.app) before clicking Update.
  • Rollback: open the Lovable history panel, find a known-good version, click Restore. Note: restoring before Cloud was added does not remove Cloud.
  • Failed deployment recovery: read the build error in the Lovable chat; fix the named file; rebuild auto-runs.
  • Cloning: use Lovable "Remix" to clone the project for a staging copy. Each remix gets its own Cloud project.

13. Backup / Export / Restore

  • Database: Cloud → Database → Backups (managed daily backups). Manual: pg_dump $SUPABASE_DB_URL > backup.sql.
  • Auth users: Cloud → Users exports as CSV. Note: password hashes do not export; users must reset on restore unless a full PG dump is restored.
  • Dossier / codex export:
    COPY (SELECT e.slug, c.key, c.value, c.version, c.created_at
    FROM entities e JOIN codex_entries c ON c.entity_id = e.id)
    TO STDOUT WITH CSV HEADER;
  • Storage: Cloud → Storage → bucket → Download (none today).
  • Command log: COPY command_log TO STDOUT WITH CSV HEADER;
  • Full restore: create a new Cloud project, run the SQL dump, re-create auth users (or restore via PG dump including auth.*), redeploy frontend, point domain.

14. Troubleshooting Table

ProblemWhat it meansCauseFix
Invalid login credentialsEmail or password does not match an account.Typo, wrong password, or account never created.Re-enter carefully or use 'Forgot password?' on /auth.
User already existsAccount with that email already exists.Previous signup with same email.Switch to Sign in. Use Forgot password if needed.
Email rate limit exceeded (429)Too many auth emails sent recently.Multiple signup/reset attempts in a short window.Wait 60 seconds. If persistent, confirm SMTP is not over quota in Cloud → Auth.
Stuck on /auth after sign inSign-in succeeds but redirect loops.Blocked cookies or invalid redirect param.Allow cookies; visit /auth?redirect=/bvc/entities directly.
'undefined' is not valid JSONLegacy command pipeline parse error.Old build cached in browser.Hard-refresh (Ctrl/Cmd+Shift+R). Already fixed in current build.
Command failedServer rejected entity.create / codex.add.Validation, duplicate slug, or signed out.Read red error text under form; fix named field and retry.
permission denied for tableRLS rejected the read or write.Role too low or not entity owner.Sign in as admin/operator. First user is auto-admin.
Entity not appearing in registryCreated entity missing from list.Silent creation failure or different user.Reload /bvc/entities; confirm session; retry with unique slug.
Dossier won't openClick on entity does not load page.Network blip or missing id.Return to /bvc/entities and click again. Sign out/in if persistent.
Dashboard blankModules show empty placeholders.No business data entered yet.Open /admin, fill dossier fields, Save.
Save button does nothingNo persistence on click.Validation error or expired session.Check red error; re-sign-in at /auth.
Locked out adminCannot access admin areas.Lost password or role lost.Reset password via /auth. If role lost, see §4 — Auth Recovery.
Edge function 401Server function returned Unauthorized.Missing or expired bearer token.Sign in again; if SSR, ensure beforeLoad gate exists.
Domain not resolvingplugone.us shows error or wrong site.DNS misconfig or SSL pending.See §11 — verify A records 185.158.133.1, wait up to 72h.

15. The Donut House Walkthrough

  1. Open /auth → Sign up with owner email and password (≥6 chars). First user becomes admin.
  2. You land on /bvc/entities. Enter slug thedonuthouse, name The Donut House. Click Run command.
  3. The dossier opens at /bvc/entities/<new-id>.
  4. Add codex entry: key address, value {"street":"123 Main","city":"Springfield"}.
  5. Add codex entry: key hours, value {"mon":"6-2","tue":"6-2"}.
  6. Publish: key visibility, value {"public": true}.
  7. Open /bvc/commands (admin) — confirm three logged commands all ok = true.

16. Operator Checklist

Intake

  • Collect business name, slug, address, hours, contact.
  • Decide visibility (public / private / draft).

Verification

  • Confirm slug uniqueness on /bvc/entities.
  • Confirm dossier shows latest version of every key.
  • Confirm command_log entry exists for every change.

Publishing

  • Add visibility codex entry.
  • Test public surface (Phase 2 QR/dossier link).
  • Print or PDF this manual after every major change for audit.

End of manual. Use the browser print dialog to save as PDF or print a hard copy.