TABLE-LIST.md row counts.
The one-paragraph version
We have a built, live, in-use system for authoring and publishing app training content. An admin writes a task-based training doc (title, summary, ordered steps, screenshots/video), it lives in four Postgres tables, and it surfaces in two places: the in-app reader at/knowledge/app/*, and — on an explicit publish action — as MDX pages
on the Mintlify docs site. There are 90 docs, 118 steps, and 124 assets in there today.
What we do not have is a learning-management system. Nothing tracks a person.
There are no courses, enrollments, assignments, quizzes, scores, completions, due dates,
or certificates. Every one of those is greenfield, and all of it is additive — it
builds on top of what exists, it does not replace it.
That distinction is the whole point of this document:
Content authoring: built. Learner tracking: not built.
The four tables
All in the PM APP Supabase project (lgveqfnpkxvzbnnwuled), domain support,
status live.
training_docs — one row per task/article (90 rows)
training_doc_steps — ordered instructions (118 rows)
training_doc_id (CASCADE), step_order, title, instruction_markdown,
expected_result, source_url, action_metadata, screenshot_asset_id → assets.
training_doc_assets — media (124 rows)
storage_bucket (default documents), storage_path, asset_type
(screenshot | image | video), caption, alt_text, step_order.
Unique on (storage_bucket, storage_path).
training_doc_relations — the training map (18 rows)
source_doc_id → target_doc_id, relation_type (related | prerequisite | next),
sort_order. Constrained against self-reference and duplicates.
Migrations (supabase/migrations/):
20260627022000_create_training_docs.sql,
20260629204500_allow_training_doc_image_assets.sql,
20260630000500_create_training_doc_steps.sql,
20260630193000_allow_training_doc_video_assets.sql,
20260630210000_extend_training_docs_relations_tool_qa.sql.
RLS — read this before you build any employee-facing feature
All four tables are admin-only viacurrent_is_app_admin(), plus a service_role
full-access policy. A normal employee cannot read training_docs directly.
The in-app reader gets around this by using a service client on the server:
docs/architecture/RLS-ACCESS-LEVEL-DECISION.md.
How content flows
- The database is the source of truth. The published MDX is a derived export, not the runtime source. The in-app reader queries Postgres.
- Publishing is an explicit action, not a sync.
POST /api/admin/training-docs/[docId]/publishrenders MDX, writes assets, and registers nav indocs.json. Nothing happens automatically on save.
Where the code lives
Nav entries:
frontend/src/lib/navigation-config.ts (Task Training, Training Docs,
Training Map, Knowledge Sources).
Three things named “training” that are NOT this
Do not confuse these — they share a word and nothing else.What is confirmed absent
No table and no code anywhere for:courses, lessons, modules, quizzes,
curriculum, enrollments, certifications, learning_paths,
training_assignments, training_completions. No /academy, /university,
/learn, or /courses route.
If you’re adding employee training — build it as a layer, not a rewrite
The existing schema is content. What’s missing is learner state. Add it alongside; do not restructuretraining_docs.
The seam is already there: training_docs.task_key and training_docs.id are the
natural join targets, and user_profiles.id is the person.
A progress layer would look roughly like:
- Unit of assignment. A single doc? Or a new
training_pathsgrouping (whichtraining_doc_relationswithrelation_type='prerequisite'already half-models — check whether that’s enough before adding a table). - RLS model. Employees read published content and read/write only their own progress. This is a real policy design, not a service-client bypass.
- Content read access.
training_docsis admin-only today. Either relax the read policy forstatus='published', or keep server-side rendering as the only read path. Pick one deliberately. - Completion semantics. Self-attested checkbox, quiz score, or observed-in-app? These have very different schemas and very different levels of effort.
Ground rules for building here
- Reuse before you add.
REUSE-FIRST-GATE.mdapplies. The editor, publish pipeline, asset handling, and reader surface all exist and are tested — compose them. - Table pages use
UnifiedTablePage. See.claude/rules/TABLE-PAGE-GATE.md. - Detail pages use the
alleato-detail-pageskill. See.claude/rules/DETAIL-PAGE-GATE.md. - Run
npm run db:typesbefore writing any query, and confirm columns againstfrontend/src/types/database.types.ts. - Any new table must be added to
docs/architecture/tables.yaml, then runnpm run db:inventoryto regenerateTABLE-LIST.md. A drift check fails otherwise. - Branch + PR. Never commit to
main. See CLAUDE.md “Git Workflow”.
Related planning docs
Historical context and prior decisions live indocs/ops/tasks/:
2026-06-27-brandon-email-training-queue.md,
2026-06-29-ai-training-doc-generation.md,
2026-06-30-repeatable-training-doc-skill.md,
2026-06-30-training-docs-app-knowledge.md,
2026-07-01-training-docs-control-plane-fields.md,
2026-07-17-training-docs-skill-gates.md.