Files
STCourses/docs/superpowers/plans/2026-10-30-studio.md
T
2026-10-30 03:11:41 +01:00

78 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# STCourses WP-C Studio Implementation Plan
> **For agentic workers:** executed inline by the WP-C agent (superpowers:executing-plans). Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Browser studio for studio-managed courses: create a course, edit the manifest, lessons (Markdown with live preview), deck links, attachments and exercises in a per-author draft worktree; every save is a commit by the author; publish merges the draft into the published branch that learners read; Sync with a remote only on request; conflicts are shown and resolved by the author, never automatically.
**Architecture:**
- `packages/feature-studio/src/server/git.ts`: a thin `git` CLI wrapper over `Bun.$` (explicit identity, no hooks, no signing) plus a per-course async mutex.
- `packages/feature-studio/src/server/store.ts`: `StudioStore`, the git model of spec section 12:
- `<dataDir>/courses/<id>`: a **bare** repo (it holds no `stcourse.yaml`, so course discovery never loads it);
- `<dataDir>/worktrees/<id>/published`: worktree on the published branch (`main`); the only tree learners read;
- `<dataDir>/worktrees/<id>/drafts/<author>`: worktree on `draft/<author>` (depth 3 under `worktrees/`, so discovery, which stops at depth 2, never loads drafts).
- Publish = `git merge --no-ff draft/<author>` inside the published worktree. A conflicting merge is aborted at once, so the published tree is never left half-merged, and the conflicting paths are returned. The author then runs "Update draft" (merge of `main`, or `origin/main` for Sync, into the draft worktree), resolves each file in the studio, and publishes again.
- Save uses optimistic concurrency: each file carries the hash of the content the editor loaded; a mismatch with the file on disk (an external edit) gives 409 with both versions. Nothing is overwritten.
- `watch.ts`: polls the published HEAD of each managed course (default every 3 s, unref'd timer) and calls `ctx.courses.reload(id)` when it changed (external commit, pull, publish).
- `feature.ts`: `studioFeature(ctx)` with `/api/studio/*` routes. Access: owner, `author` role, or a `course_authors` row for that course. Courses not managed by the studio (overlays, fixture/imported folders) are listed read-only: "This course is managed outside STCourses".
- `draft-writer.ts`: `createDraftWriter(ctx)` for WP-D.
- Web (`src/web/*`): `/studio` (course list, New course) and `/studio/:courseId` (outline sidebar, editors, status bar with Publish / Update draft / Sync and the conflict view). Work look from the mockup. The lesson preview sits behind a `LessonPreview` adapter; v1 is a small safe Markdown renderer; Phase 2 swaps in the learner `LessonView` (contract request).
**Tech Stack:** Bun 1.3, TypeScript strict, git CLI 2.4x via `Bun.$`, React 19 + React Router 6, `@stc/course-format` for validation, `@stc/ui` primitives.
**Spec:** sections 5, 9.1, 12 (git model), 13. Brief: `.superpowers/sdd/2026-10-30-stcourses/phase-1-brief.md` (WP-C).
## Global Constraints
Inherits every item of the Global Constraints in `docs/superpowers/plans/2026-10-30-stcourses.md`. Additionally:
- Never write into a course whose root is outside `<dataDir>` (overlays) — the studio refuses writes for non-managed courses.
- Paths written through the studio must be relative, stay inside the worktree, and never touch `.git`.
- No new DB tables (authors use the existing `course_authors`); `migrationsDirs` is exported empty.
## Review Focus
1. A conflicting external commit to `main` is detected at publish and shown; the published tree keeps the external content (test: acceptance).
2. Drafts never reach learners: before publish the `CourseRepository` still serves the published content (test: acceptance).
3. An external edit to a draft file between load and save gives 409, not an overwrite (test: store).
4. Paths with spaces and non-ASCII characters save, commit and publish (test: store).
5. Path traversal (`..`, absolute, `.git/…`) is rejected (test: store).
## Tasks
### Task 1: git wrapper and StudioStore (create, drafts, save)
**Files:** Create `packages/feature-studio/src/server/{git.ts,store.ts,paths.ts}`; Test `packages/feature-studio/test/store.test.ts`.
**Interfaces (Produces):** `createStudioStore(dataDir): StudioStore` with `createCourse`, `isManaged`, `listManaged`, `ensureDraft`, `readFile`, `listFiles`, `saveFiles`, `status`.
- [ ] Step 1: failing tests: create gives bare repo + published worktree with a valid manifest; draft worktree on `draft/<author>`; save commits with the author's name/email; stale base hash gives a conflict; traversal rejected; non-ASCII path works.
- [ ] Step 2: run, FAIL. Step 3: implement. Step 4: PASS. Step 5: commit.
### Task 2: publish, update draft, conflict resolution, sync
**Files:** extend `store.ts`; Test `packages/feature-studio/test/publish.test.ts`.
- [ ] Step 1: failing tests: publish merges; external conflicting commit gives `{ok:false, conflicts}` and leaves `main` untouched; dirty published tree refuses publish; update draft shows ours/theirs; resolve + publish succeeds; abort; Sync with a local bare remote (pull fast-forward, push, conflict reported); no remote gives an error.
- [ ] Steps 2–5 as above.
### Task 3: watcher and draft writer
**Files:** `src/server/{watch.ts,draft-writer.ts}`; Test `test/watch.test.ts`, `test/draft-writer.test.ts`.
- [ ] Failing tests: external commit triggers `courses.reload`; `writeDraftFiles` commits on `draft/<author>` and the published tree is unchanged. Implement, pass, commit.
### Task 4: HTTP feature and acceptance
**Files:** `src/server/feature.ts`, `src/index.ts`; Test `test/feature.test.ts`, `test/acceptance.test.ts`.
- [ ] Failing tests: 401/403 for anonymous and learners; overlay course read-only; create → edit lesson → publish → learner sees it via `CourseRepository` and `/api/media`; conflicting external commit shown, not overwritten; manifest validation errors returned. Implement, pass, commit.
### Task 5: Studio web UI
**Files:** `src/web/{api.ts,markdown.ts,preview.tsx,StudioHome.tsx,CourseStudio.tsx,editors/*.tsx,studio.css}`, `src/web.ts`; Test `test/markdown.test.ts`.
- [ ] Failing test for the Markdown renderer (escaping, headings, fences, lists, links without `javascript:`). Implement UI. Commit.
### Task 6: wiring, dev server, contract requests, verification
- [ ] Append to `apps/web/src/features.ts`; dev entry `packages/feature-studio/dev/serve.ts`; `docs/contract-requests/studio.md`; run `bun test`, typecheck, Postgres run; headless Chrome screenshots of `/studio` and `/studio/:id`; report.