Add arbitrary pages feature design spec
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3d8c6f5345
commit
a00567a756
148
docs/superpowers/specs/2026-03-22-pages-design.md
Normal file
148
docs/superpowers/specs/2026-03-22-pages-design.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# Arbitrary Pages Feature Design
|
||||
|
||||
## Overview
|
||||
|
||||
Allow users to create arbitrary pages for their site. Each page has a title, user-editable slug, markdown body, page type, nav visibility flag, and sort order. Pages are a separate entity from posts with their own admin section and generated site template. Pages rendered at conflicting slugs silently override auto-generated content.
|
||||
|
||||
## Data Layer
|
||||
|
||||
### New `pages` table
|
||||
|
||||
```sql
|
||||
CREATE TABLE pages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
site_id INTEGER NOT NULL,
|
||||
guid TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
slug TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
page_type INTEGER NOT NULL DEFAULT 0,
|
||||
show_in_nav INTEGER NOT NULL DEFAULT 0,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (site_id) REFERENCES sites (id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX idx_pages_site ON pages (site_id);
|
||||
CREATE UNIQUE INDEX idx_pages_guid ON pages (guid);
|
||||
CREATE UNIQUE INDEX idx_pages_site_slug ON pages (site_id, slug);
|
||||
```
|
||||
|
||||
### Model
|
||||
|
||||
```go
|
||||
type Page struct {
|
||||
ID int64
|
||||
SiteID int64
|
||||
GUID string
|
||||
Title string
|
||||
Slug string
|
||||
Body string
|
||||
PageType int
|
||||
ShowInNav bool
|
||||
SortOrder int
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
```
|
||||
|
||||
Page type constants: `PageTypeNormal = 0` (extensible later for archive, search, etc.).
|
||||
|
||||
### SQL queries
|
||||
|
||||
- `SelectPagesOfSite(siteID)` — all pages for a site, ordered by `sort_order ASC`
|
||||
- `SelectPage(id)` — single page by ID
|
||||
- `SelectPageByGUID(guid)` — single page by GUID
|
||||
- `InsertPage` — create new page, returns ID
|
||||
- `UpdatePage` — update page fields
|
||||
- `DeletePage(id)` — delete page
|
||||
- `UpdatePageSortOrder(id, sortOrder)` — update sort order for a single page
|
||||
|
||||
## Admin Section
|
||||
|
||||
### Navigation
|
||||
|
||||
Add "Pages" item to the admin nav bar (`views/_common/nav.html`), linking to `/sites/:siteID/pages`.
|
||||
|
||||
### Routes
|
||||
|
||||
```
|
||||
GET /sites/:siteID/pages - List pages
|
||||
GET /sites/:siteID/pages/new - New page form
|
||||
GET /sites/:siteID/pages/:pageID - Edit page form
|
||||
POST /sites/:siteID/pages - Create/update page
|
||||
DELETE /sites/:siteID/pages/:pageID - Delete page
|
||||
POST /sites/:siteID/pages/reorder - Update sort order (AJAX)
|
||||
```
|
||||
|
||||
### Page list view (`views/pages/index.html`)
|
||||
|
||||
- Lists pages ordered by `sort_order`
|
||||
- Each row shows title, slug, and nav visibility indicator
|
||||
- Drag-and-drop reordering via Stimulus + HTML drag API
|
||||
- On drop, sends new order to `POST /pages/reorder` via AJAX
|
||||
- "New Page" button
|
||||
|
||||
### Page edit form (`views/pages/edit.html`)
|
||||
|
||||
Two-column layout mirroring the post edit form:
|
||||
|
||||
**Main area (left):**
|
||||
- Title input
|
||||
- Body textarea (markdown)
|
||||
|
||||
**Sidebar (right):**
|
||||
- Slug (editable text input, auto-derived from title via client-side JS, user can override)
|
||||
- Page Type (select dropdown, just "Normal" for now)
|
||||
- Show in Nav (checkbox)
|
||||
|
||||
Save button below.
|
||||
|
||||
### Service layer (`services/pages/`)
|
||||
|
||||
- `Service` struct with DB provider dependency
|
||||
- `CreatePage(ctx, params)` — generates GUID, derives slug from title if not provided, sets timestamps
|
||||
- `UpdatePage(ctx, params)` — updates fields, sets `updated_at`
|
||||
- `DeletePage(ctx, pageID)` — deletes page
|
||||
- `ListPages(ctx)` — returns all pages for the site from context, ordered by `sort_order`
|
||||
- `GetPage(ctx, pageID)` — returns single page
|
||||
- `ReorderPages(ctx, pageIDs []int64)` — accepts ordered list of page IDs, updates `sort_order` for each (sort_order = index in list)
|
||||
|
||||
### Handler (`handlers/pages.go`)
|
||||
|
||||
- `PagesHandler` struct with `PageService`
|
||||
- Standard CRUD handlers following the existing posts handler pattern
|
||||
- `Reorder` handler accepts JSON array of page IDs, calls `ReorderPages`
|
||||
|
||||
## Generated Site
|
||||
|
||||
### Template
|
||||
|
||||
New template `pages_single.html` — receives rendered page HTML, rendered inside `layout_main.html` (same wrapping as posts).
|
||||
|
||||
Template data:
|
||||
```go
|
||||
type pageSingleData struct {
|
||||
commonData
|
||||
Page *models.Page
|
||||
HTML template.HTML
|
||||
}
|
||||
```
|
||||
|
||||
### Builder changes
|
||||
|
||||
New method `renderPages` on the builder:
|
||||
- Iterates all pages from `pubmodel.Site.Pages`
|
||||
- For each page, renders markdown body and writes to the page's slug path using `createAtPath`
|
||||
- Pages are rendered **after** all other content (posts, post lists, categories, feeds, uploads, static assets)
|
||||
- This ensures pages at conflicting slugs silently overwrite auto-generated content
|
||||
- Implementation: `renderPages` runs as a sequential step after `eg.Wait()` returns in `BuildSite`
|
||||
|
||||
### Publisher changes
|
||||
|
||||
- `pubmodel.Site` gets a new `Pages []models.Page` field
|
||||
- The publisher fetches all pages for the site via `SelectPagesOfSite` and populates this field
|
||||
|
||||
## Approach
|
||||
|
||||
Pages are a separate entity from posts with their own table, service, handler, and templates. The override mechanism is file-system-based: the site builder renders pages last, so any page slug that conflicts with an auto-generated path wins by overwriting the file. The `show_in_nav` field is stored and editable in admin but not yet consumed by the generated site layout — that integration is deferred for a future change.
|
||||
Loading…
Reference in a new issue