Table Repository — PRD
Table Repository
Scope
MVP — Tables become first-class, reusable content fundamentals:
- Create / edit a Table as a standalone entity (PlateJS table object),
with cell background fill (fill the cell, not just text color),
title(required),hide_title, and optionaldescription. - Repository list + search — browse all Tables; search by
short_uidandtitle. - Insert inline into a Block or MCQ — reference a repository Table inside
a Block’s or MCQ’s PlateJS rich text via a custom Table-reference tag
(the same pattern as the existing inline-image / image-bank flow), by
short_uid. - Cross-linking & “where used” — one Table referenceable by many
Blocks/MCQs; the Table surfaces its
linked_entities(where it is attached). - Filter the list by L1 / L2 / L3 usage — via denormalized taxonomy usage on the Table (Option 1), derived from each linked entity’s immutable chain (see Consistency rule).
- Status lifecycle —
published/archived; no hard delete (archive is the only termination, per platform rule). A Table can be archived only when it has no linked entities (linked_entitiesis empty). While any linkage remains, archive is blocked — the editor is shown the list of linked Block/MCQs and must remove all linkages first, then archive.
Data Model (requirements-level)
The shape of a Table. Consistency rule (decided): all denormalized fields are maintained synchronously through a single write path — one operation updates the entity’s rich text (PlateJS ref node),
linked_table_ids,linked_entities, andused_in_l*_idstogether. No async / background recompute. The only trigger is link / unlink of a Table to a Block/MCQ — Block→Docket moves and Docket re-parenting do not exist (disallowed by the platform), so a linked entity’s taxonomy chain is immutable andused_in_l*_idsnever needs re-deriving after the fact.
| Field | Type | Notes |
|---|---|---|
id | string (ObjectId) | System-generated PK. |
short_uid | string | System-generated; prefix TBL (e.g. TBL5RT8W). Unique per Course. |
course_id | int | Course scope (UPSC=1, TEST=80085). |
title | string | Required. |
hide_title | boolean | If true, title is not rendered as a caption. |
description | string | Optional. |
table_content | string | PlateJS table JSON (same structure as an inline Block table; supports cell fill). |
linked_entities | array | [{ content_id, content_type: "block" | "mcq" }] — where the Table is attached (denormalized to save read cost). |
used_in_l1_ids / used_in_l2_ids / used_in_l3_ids | array | Denormalized taxonomy usage derived from linked_entities chain; powers the L1/L2/L3 filter (Option 1). |
status | enum | published | archived. |
created_at / updated_at | int (Unix ms) | Audit. |
created_by / updated_by | string (User ObjectId) | Audit refs. |
is_deleted | boolean | Legacy soft-delete; deferred to Sync convention. |
Block & MCQ changes
linked_table_ids— on both Block and MCQ — denormalized list of Tables referenced in that entity’s rich text (the inverse side ofTable.linked_entities; both stored to save read cost).- A custom PlateJS Table-reference node in the Block’s / MCQ’s rich text
(mirrors the inline-image ref node), which drives
linked_table_ids.
Delivery Milestones
| # | Milestone | Outcome | Status | Plan |
|---|---|---|---|---|
| 1 | Table entity + repository store | Editors can create, edit, and list standalone Tables (with cell fill) | pending | — |
| 2 | Search | Editors find a Table by short_uid or title | pending | — |
| 3 | Inline reference into Block & MCQ | Editors insert a repository Table into a Block or MCQ via a PlateJS Table tag; linked_table_ids / linked_entities stay in sync | pending | — |
| 4 | Cross-linking & “where used” | A Table shows every Block/MCQ it is attached to | pending | — |
| 5 | L1/L2/L3 usage filter | Editors filter the Table list by taxonomy of usage (denormalized) | pending | — |
| 6 | Status lifecycle | Tables can be published/archived; no hard delete | pending | — |
Open Questions
- None remaining — all resolved during PRD review.
Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
Ref-node ↔ denormalization drift. PlateJS Table ref nodes in a Block/MCQ’s rich text and the denormalized lists (linked_table_ids, linked_entities, used_in_l*_ids) can diverge if updated separately. | Medium | Medium — wrong “where used” / filter results | Single write path (confirmed): one atomic operation updates rich text + all denormalized lists together; validate on save. |
| Archive friction. A Table reused across many entities can only be archived after every reference is unlinked by hand. | Low | Low — editor effort | The “where used” view (milestone 4) lists every linkage so the editor can unlink quickly before archiving. |