← Command Center

Page Hierarchy, Group Pages & Table View

Version 1.0 Date 2026-03-09 Draft Owner MasteryMade Engineering

1. Problem Statement

NowPage supports hierarchical URLs via parent_page_id chains, but three capabilities are missing:

  1. Group pages are dead ends. To create a URL like /prds/vault, you need a parent page called prds — but that page must have custom HTML. If you don't write content for it, /prds/ is a blank page. Users avoid creating groups because the parent page is wasted effort.
  2. The list view is a sketch, not a tool. PageManager.tsx shows page rows with name + status + link. No columns, no sorting, no search, no folder info, no dates. Once you have 30+ pages, the list view is useless and you default to the grid (which is slow to scan at scale).
  3. No UX for managing hierarchy. The tree view supports drag-to-reparent, but there's no guided flow for "create a group with children" or "convert a flat page into a group." Users who want domain/group/page/subpage must manually set parent_page_id on each page.

What's broken today:


2. Current State

What Exists

ComponentStatusLocation
parent_page_id chainWorkingpages table, app/api/pages/route.ts
full_path resolutionWorkingapp/serve/[[...slug]]/route.ts lines 300-322
Three-level URL supportWorkingfull_path = "group/page/subpage" resolves correctly
Grid viewWorkingPageCardGrid.tsx — thumbnail cards
List viewMinimalPageManager.tsx — bare rows, no columns/sort/search
Tree viewWorkingPageTree.tsx — expandable hierarchy, drag-reparent
Folder metadataWorkingfolders table, folder_id on pages (org only, not routing)
View mode switcherWorkingDashboardContent.tsx line 636 — grid/list/tree buttons
Neural registryWorkingDomain-wide only via is_neural_registry flag

What's Missing

ComponentImpact
Auto-render template for group pagesGroups require custom HTML or they're blank
hc_type: "group" in HC metadataNo semantic distinction for group pages
Focused neural registriesRegistries are domain-wide, not group-scoped
Table view with sortable columnsList view unusable at scale
Search across pagesNo way to find a page by name/slug/path
"Create group" guided flowMust manually set parent_page_id per-page
Breadcrumb display in tableNo hierarchy visibility in list/table view

3. The Three URL Structures

3.1 Structure Types

PatternExampleUse Caseparent_page_id
Flat domain.com/rick-meekins Most pages. Single artifact, no grouping needed. null
Grouped domain.com/prds/vault Related pages under a category. The group page (/prds/) serves as an index with its own content or auto-rendered. Page's parent = group page
Nested domain.com/prds/vault/appendix Sub-pages within a grouped page. For multi-page templates, deep-dive sections, appendices. Page's parent = page within group

3.2 How This Already Works (Routing)

The serve route (app/serve/[[...slug]]/route.ts) already handles all three:

Request: domain.com/prds/vault/appendix
  1. Try full_path = "prds/vault/appendix"  →  match? serve it
  2. Try slug = "prds/vault/appendix"       →  match? serve it
  3. 404

The full_path is built automatically from the parent_page_id chain when a page is created/updated in app/api/pages/route.ts.

No routing changes needed. This PRD focuses on the auto-render template, the table view, and the dashboard UX.

4. Auto-Rendering Group Template

4.1 When It Triggers

A page is considered a "group page" when ALL of these are true:

4.2 HC Metadata Extension

{
  "hc_version": "1.3.3",
  "hc_type": "group",
  "title": "Platform PRDs",
  "group_principle": "These PRDs define the NowPage platform build sequence.
                      Each phase unlocks a multiplier on the one before it.",
  "neural_registry": true,
  "child_order": "manual",
  "gate": null
}

New fields:

FieldTypeDescription
hc_type"group"Identifies this as a group page
group_principlestringThe "why" statement displayed at top of auto-render
neural_registrybooleanIf true, this group's context injects into child page MCP requests
child_order"manual" | "alpha" | "newest" | "oldest"How children are sorted in auto-render
gatenull | "password"Optional password gate on the group page

4.3 Auto-Render Template Design

MasteryMade design system. Same fonts (Bebas Neue / DM Sans / DM Mono), same color palette (cream/ink/amber). The template structure:

┌─────────────────────────────────────────────┐ │ GROUP TITLE [N pages] │ │ domain.com/group-slug │ ├─────────────────────────────────────────────┤ │ │ │ ┌─ PRINCIPLE BOX (dark bg, amber accents) ─┐ │ │ {group_principle text} │ │ │ First-principles "why" for this group │ │ └──────────────────────────────────────────┘ │ │ │ PAGES │ │ ┌──────────────────────────────────────────┐ │ │ Page Title Mar 8 │ │ │ {description from HC metadata} │ │ │ /group/page-slug [tags] │ │ ├──────────────────────────────────────────┤ │ │ Page Title Mar 7 │ │ │ {description from HC metadata} │ │ │ /group/page-slug [tags] │ │ ├──────────────────────────────────────────┤ │ │ ... │ │ └──────────────────────────────────────────┘ │ │ │ Updated March 9, 2026 │ └─────────────────────────────────────────────┘

4.4 Data Source for Auto-Render

The template renders from child page metadata. No LLM calls, no generation. Pure data assembly:

interface GroupChildData {
  page_name: string        // from pages table
  slug: string             // from pages table
  full_path: string        // from pages table
  description: string      // from HC metadata in html_content (parsed)
  tags: Tag[]              // from page_tags join
  created_at: string       // from pages table
  updated_at: string       // from pages table
  is_published: boolean    // from pages table
  children_count: number   // count of this page's own children
}
HC metadata extraction: Parse <script id="hc-metadata" type="application/json"> from each child page's html_content to get title and description. If no HC metadata exists, fall back to page_name and show no description.

4.5 Implementation Location

The auto-render happens in the serve route, not as stored HTML:

// app/serve/[[...slug]]/route.ts

// After finding the page...
if (isGroupPage(page)) {
  const children = await getChildPages(page.id, supabase)
  const metadata = parseHCMetadata(page.html_content)
  const html = renderGroupTemplate({
    title: page.page_name,
    principle: metadata?.group_principle,
    children,
    childOrder: metadata?.child_order || 'manual',
    domain: domain.full_domain,
    groupSlug: page.slug,
    updatedAt: page.updated_at
  })
  return new Response(html, {
    headers: { 'Content-Type': 'text/html' }
  })
}

function isGroupPage(page: Page): boolean {
  const metadata = parseHCMetadata(page.html_content)
  if (metadata?.hc_type === 'group') return true
  if (!page.html_content || page.html_content.trim().length < 100) {
    // Check if it has children
    return true  // caller must verify children exist
  }
  return false
}

4.6 Group Page with Custom Content

If a group page has hc_type: "group" AND substantial html_content, serve the custom HTML. The auto-render is a fallback for empty groups, not a replacement for authored content. This means:


5. Focused Neural Registries

5.1 How It Works Today

A page with is_neural_registry: true serves as a domain-wide context source. MCP tools can request /api/registry?domain=X and get all registry page content concatenated.

5.2 Group-Scoped Registries

When a group page has neural_registry: true in its HC metadata:

  1. The group page's content (principle + child descriptions) becomes a focused registry
  2. MCP requests for any child page automatically include the parent group's registry context
  3. This is additive — domain-wide registries still work, group registries layer on top

MCP endpoint change:

GET /api/registry?domain=X&page=prds/vault

Response:
{
  "domain_registry": "...",           // existing domain-wide context
  "group_registry": "...",            // NEW: /prds/ group context
  "group_principle": "These PRDs...", // NEW: the group's "why"
  "siblings": [                       // NEW: other pages in the group
    { "slug": "teams", "title": "Teams PRD" },
    { "slug": "mcp", "title": "MCP PRD" }
  ]
}

5.3 Parent Chain Walk

For nested pages (domain/group/page/subpage), the MCP endpoint walks up the parent_page_id chain and collects registries at each level:

subpage → parent page → group page → domain
                                ↑ group registry
                         ↑ (no registry, skip)
  ↑ (the requested page)

This is a single recursive query, not multiple round trips:

WITH RECURSIVE ancestors AS (
  SELECT id, parent_page_id, html_content, page_name
  FROM pages WHERE id = $requested_page_id
  UNION ALL
  SELECT p.id, p.parent_page_id, p.html_content, p.page_name
  FROM pages p
  JOIN ancestors a ON p.id = a.parent_page_id
)
SELECT * FROM ancestors WHERE id != $requested_page_id;
Filter results for those with neural_registry: true in their HC metadata.

6. Table View (List View Upgrade)

6.1 Replace PageManager.tsx

The current PageManager.tsx (168 lines) becomes PageDataTable.tsx. The view mode switcher in DashboardContent.tsx stays as-is — viewMode === 'list' renders the new component.

6.2 Table Columns

ColumnSourceWidthSortableNotes
Namepage_nameflexYesBold. Shows hierarchy indent for children.
Pathfull_path or slug200pxYesMonospace. Clickable link to live URL.
Folderfolder?.name100pxYesGray if "—" (no folder).
Statusis_published, is_homepage80pxYesBadge: Home (blue), Live (green), Draft (gray).
Tagstags[]120pxNoColor dots. Hover for names.
Updatedupdated_at100pxYes (default desc)Relative: "2h ago", "Mar 7".
Actions80pxNoView / Edit / Delete icons.

6.3 Search

Text input above the table. Filters on:

Client-side filtering on the already-loaded filteredPages array. No API call needed.

6.4 Sort State

const [sortColumn, setSortColumn] = useState<
  'name' | 'path' | 'folder' | 'status' | 'updated'
>('updated')
const [sortDirection, setSortDirection] = useState<
  'asc' | 'desc'
>('desc')

Click column header to sort. Click again to toggle direction. Active column header shows arrow indicator.

6.5 Hierarchy Indent

Pages with parent_page_id show indented in the table when sorted by name or path:

Vault PRD                      /prds/vault
  Vault Appendix               /prds/vault/appendix
Teams PRD                      /prds/teams
Rick Meekins                   /rick-meekins
Indent is 24px per level. Calculated from full_path depth (count of / separators).

6.6 Row Actions

ActionBehavior
ViewOpens https://{domain}/{full_path} in new tab
EditCalls onEditPage(page) (existing handler)
DeleteCalls onDeletePage(page.id) (existing handler) with confirm

6.7 Empty States

6.8 Responsive

Below 768px:

6.9 Component Interface

interface PageDataTableProps {
  pages: (Page & { domain?: Domain })[]
  domains: Domain[]
  onEditPage: (page: Page) => void
  onDeletePage: (pageId: string) => void
  onViewPage: (page: Page) => void
}
Slots directly into the existing DashboardContent.tsx where <PageManager> is rendered today (line 724-730).

7. Dashboard UX for Hierarchy Management

7.1 "Create Group" Flow

Add a "Create Group" option to the + New Page / + Build Page area:

+ Build Page + New Page + Group

Clicking "+ Group" opens a minimal dialog:

┌── Create Page Group ───────────────────┐ │ │ │ Group Name: [Platform PRDs ] │ │ Slug: [prds ] │ │ │ │ Principle (optional): │ │ [These PRDs define the NowPage... ] │ │ [platform build sequence. ] │ │ │ │ [ ] Neural Registry │ │ [ ] Password Gate │ │ │ │ [Cancel] [Create Group] │ └────────────────────────────────────────┘

This creates a page with:

7.2 "Add to Group" in Tree View

The existing tree view already supports drag-to-reparent. Add a context menu option:

Right-click page → "Move to Group..." → dropdown of existing group pages
This sets parent_page_id and rebuilds full_path — already working in the API.

7.3 "Add to Group" in Table View

In the table view, add a "Group" column option or inline action:

7.4 Group Page Indicators

In all views (grid, table, tree):

ViewHow groups appear
GridGroup card has a folder icon + child count badge. Thumbnail shows auto-rendered template preview.
TableGroup rows have bold name + child count. Children indented below.
TreeAlready shows hierarchy. Group pages get a distinct icon (folder vs document).

7.5 Breadcrumb in Page Editor

When editing a page that's inside a group, show breadcrumb navigation:

domain.com / prds / vault
             ↑ clickable (goes to group page editor)
This already partially exists in the tree view but not in the editor modal.

8. Reader Experience (Public-Facing)

8.1 Auto-Rendered Group Page

When a reader visits domain.com/prds/:

8.2 Child Page Navigation

When a reader is on a child page (domain.com/prds/vault):

Decision: defer child nav injection to a future iteration. It requires modifying served HTML, which is risky. The group page itself provides sufficient navigation.

8.3 Reader URL Experience

URLWhat they see
domain.com/prds/Auto-rendered group index with principle + child list
domain.com/prds/vaultThe Vault PRD (custom HTML, served as today)
domain.com/prds/vault/appendixSub-page of Vault (custom HTML, served as today)
domain.com/rick-meekinsFlat page (no group, served as today)

9. Implementation Sequence

Phase A: Table View

No dependencies, immediate value

  1. Create PageDataTable.tsx with columns, sorting, search
  2. Replace <PageManager> reference in DashboardContent.tsx
  3. Preserve all existing props/handlers (delete, edit, view)

Files changed: components/PageDataTable.tsx (new), components/DashboardContent.tsx (swap component)

Files removed: components/PageManager.tsx (replaced)

Effort: 2-3 hours

Phase B: Group Page Auto-Render

Serve route + HC metadata

  1. Add hc_type: "group" to HC metadata spec
  2. Add parseHCMetadata() utility (extract JSON from <script id="hc-metadata">)
  3. Add renderGroupTemplate() in serve route (static HTML builder)
  4. Add isGroupPage() check in serve route before normal page serve
  5. Query children when group page detected

Files changed: app/serve/[[...slug]]/route.ts, new utility lib/hc-metadata.ts

No migrations. All data is in existing html_content and pages table.

Effort: 3-4 hours

Phase C: "Create Group" UX

Dashboard dialog

  1. Add GroupCreateDialog.tsx component
  2. Add "+ Group" button to dashboard header
  3. Wire to existing POST /api/pages with HC metadata in html_content
  4. Add group indicators to grid/table/tree views

Files changed: components/GroupCreateDialog.tsx (new), components/DashboardContent.tsx, components/PageDataTable.tsx, components/PageCardGrid.tsx

Effort: 2-3 hours

Phase D: Focused Neural Registries

MCP endpoint

  1. Add group registry context to /api/registry endpoint
  2. Add parent chain walk (recursive CTE query)
  3. Filter ancestors for neural_registry: true
  4. Include group_principle and siblings in response

Files changed: app/api/registry/route.ts (or wherever registry endpoint lives)

Effort: 2 hours


10. What We're NOT Building

ExcludedWhy
Folder-based URL routingparent_page_id chain already handles this. Folders stay as dashboard metadata.
Child page navigation injectionModifying served HTML at render time is risky. Group page provides navigation.
Configurable auto-render themesOne template (MasteryMade). No theme picker.
Pagination on group pagesGroups won't have 100+ children. Simple list is sufficient.
Drag-drop in table viewTree view already handles reparenting. Table is for scanning.
Fourth view modeTable replaces list. No new button needed.

11. Success Criteria