# Admin wiring — content types end-to-end (2026-07-22)

## What
The `/admin/admin.html` SPA at `oscar-web.freshvibeapps.com/admin/` was a
half-built admin UI: it had 7 nav links, a dashboard, search, settings, etc.,
but the Content view was empty. The 5 hand-curated content types from the
FreshVibe bridge (Listing, BlogPost, Product, Recipe, Event) were not
loaded into the admin.

## Why
The admin.js bootstrap didn't fetch the bridge content-types or register
them as Schemas. So the Content view (which calls `allSchemas()`) returned
an empty list.

## How (the fix)

1. **admin.js** (158 lines) — fetches `vibecoder.freshvibeapps.com/api/agent/content-types`,
   converts each to a Schema instance, registers it, sets up the stubs adapter,
   and registers parametric content routes (list / edit / new per schema).
   Key behaviours:
   - Bridge field types mapped to admin field types (`text` → `string`, etc.)
   - Each schema gets an `id` field auto-injected (admin requires record.id)
   - Routes registered AFTER initRouter (which rebuilds _routeTable)
   - Capture-phase pattern listener for `admin/content/<schema>/edit/<id>`
     (the router's exact-key dispatch doesn't support wildcards)

2. **form-bindings.js** — patched the `bindForm()` early-return to allow
   re-binding when the new handlers include `onSubmit` or `onCancel`.
   Without this, the list view's `bindForm()` set `data-cms-bound=1` on
   the MAIN container, blocking the editor view's bind.

3. **CORS fix on the bridge** — `vibecoder.freshvibeapps.com/api/agent/*`
   was sending `Access-Control-Allow-Origin: *` (hardcoded), conflicting
   with nginx's dynamic `$http_origin` header. Browsers reject
   multi-value ACAO. Fixed in `server.js`:
   ```js
   app.use(cors({
     origin: false,  // nginx sets ACAO, bridge must not
     ...
   }));
   ```

## Verified end-to-end

- Open `https://oscar-web.freshvibeapps.com/admin/admin.html`
- Click Content → see 5 schemas (Listings, BlogPosts, Products, Recipes, Events)
- Click Listings → see list view (empty)
- Click + New Listing → see form (ID, Title, Price, Photos, Location, Description, Bedrooms, Bathrooms, Square feet)
- Fill form, click Create → record saved, redirects to edit page
- Navigate to Listings → see the saved row in the table

## Bugs hit (and fixed)

1. **CORS multi-value** — bridge + nginx both set ACAO. Set bridge's cors
   to `origin: false`.
2. **Schema id is random PB string** — admin needs snake_case. Used
   `it.name.toLowerCase().replace(/\s+/g, '_')` instead of `it.id`.
3. **Field type 'text' rejected** — admin doesn't accept 'text'. Type-map.
4. **Missing record.id** — bridge schemas don't have `id`. Auto-inject.
5. **allSchemas returns Object, not Array** — views use `for...of`. Use
   `schemaList()` (Array) in stubs.
6. **initRouter rebuilds _routeTable** — register routes AFTER initRouter.
7. **bindForm early-return** — patched to allow re-binding.
8. **Router doesn't support wildcards** — capture-phase pattern listener.

## Files (not in this repo — live on VPS)

- `oscar-web/app-fragments/admin/admin.js` (158 lines, GLM-generated v8)
- `oscar-web/app-fragments/admin/form-bindings.js` (patched)
- `/opt/vibecoder-bridge/server.js` (cors config)

These files live in the served oscar-web directory, not in a GitHub
repo. The proper home would be `avidtech6/freshvibe-cms` under
`app-fragments/admin/`, but that requires a separate session to
properly migrate.
