# Template Library: explore, vote, comment, share — Design Document **Status:** Phase 1 built (2026-09-19); phases 2 and 3 are still design **Last updated:** 2026-09-19 **Scope:** Turns saved templates from a private list into a library people can browse, judge and pass around: inside one instance first, then between instances as a file or link, then as a community gallery. Touches the template model and API, a new Library page, project creation, and (phase 3) the GitHub repository. --- ## 0. Problem / motivating requirements The README's promise is to "share the successful result" (at the time of writing: "keep the plan that worked"). Today that plan can only be kept by the one person who saved it. A team cannot see each other's templates, cannot tell a good one from an abandoned one, and cannot hand one to someone at another company. - **R1. Explore.** Browse every template you are allowed to see, with search, tags and sorting, and **see the plan before using it**: its tracks, durations, dependencies and milestones. - **R2. Judge.** Signal which templates are good. Votes are one signal; the better one is a **track record**: how many projects were started from it, how many finished, and how they ran against the plan. That is what "proven" means. - **R3. Discuss.** Comment on a template: what worked, what to change, what it assumes. - **R4. Share inside an instance.** Publish a template to your team or to everyone on the instance. - **R5. Share outside it.** Hand a template to anyone as a file or a link, with no account on your instance, and import one you were given. - **R6. Improve over time.** Save a better version of a template without losing its votes, comments and history. "Save it again if you learned something." - **R7. Nothing private leaks.** A template made from a real project can carry notes, to-do titles and names. Publishing must show exactly what will be shared and let the author strip it. - **R8. Self-hosted first.** Everything in phases 1 and 2 works on an instance with no internet. Not in scope: a hosted marketplace with accounts, paid templates, ratings of people, or real-time co-editing of a template. ## 1. Current state (grounding) - **`ProjectTemplate`** (`backend/projects/models.py`): `name`, `description`, `owner`, `categories` (JSON), `tasks` (JSON), `created_at`. **Strictly private**: every query filters `owner=request.user` (`_saved_items`, `_resolve_template`, `destroy` in `backend/projects/views.py`). No visibility, tags, slug, version or link to a parent. (Its docstring still lists `percent_complete` in the task shape, which templates no longer carry; fix when this is built.) - **Built-ins** live in code (`templates_builtin.py`, 21 of them) and are addressed by key (`builtin:`); saved ones by `saved:`. `HiddenBuiltinTemplate` lets staff retire a built-in. - **The template spec** (`spec_from_project` in `backend/projects/templates.py`), as of 2026-09-19: tracks; events with relative start, duration, notes, key-milestone flag and dependencies by index; each event's to-dos with due dates as day offsets. It deliberately drops dates, progress, assignees and members. **It is already a portable, self-contained JSON document**, which makes R5 cheap. - **API** (`TemplateViewSet`): `GET /api/templates/` (built-ins + your own), `POST` (save from a project you belong to), `DELETE`, `POST …/instantiate/`. The list returns only name, description and counts; **there is no endpoint that returns a template's content**, so nothing can preview one today. - **Nothing records provenance.** A project created from a template keeps no reference to it, so "how many times was this used, and did it work?" cannot be answered (R2). - **UI:** `TemplateModal.jsx`, a picker opened from the dashboard's "From Template". No page. - **Building blocks that exist:** the comment pattern (`Comment`, author-or-owner moderation); throttling (`password_reset.py`); file downloads in `api.js`; `ReportTimeline.jsx`, which already draws a compact timeline from plain data and can draw a template preview; the rule that the people directory only reveals people you already work with (`directory.py`), which a public author name on a library entry would cut across. ## 2. Prior art / research *From working knowledge of these products, not fresh testing. Confirm the specifics worth copying before building.* - **Notion, Trello, Asana, Airtable Universe, Miro:** a gallery with categories, a preview you can look around in before copying, a "use this template" button, a creator name and a usage count. Usage count is the number people actually trust; star ratings are mostly noise. - **Grafana dashboards, Home Assistant blueprints:** the pattern for *self-hosted* software. The artifact is a JSON/YAML file; you share it by URL or ID and the app has an "import from URL" box. Reviews and download counts live on the community site, not in each install. - **Obsidian community plugins:** the cheapest possible community registry. One JSON index in a GitHub repository; additions arrive as pull requests and are reviewed; every install reads the index over HTTPS. No server, no accounts, moderation by code review, and the project's repository gains contributors. This is the model for phase 3. - **Upvotes only.** Communities that allow downvotes on people's work get less of it. One upvote per person, plus usage, is enough to sort by. - **What nobody offers** and this app uniquely can: a track record computed from real runs of the plan. A template that says "started 14 times, finished 11, typically runs 6% long" is a different kind of object from one with 40 likes. ## 3. Proposed design ### 3.1 Three rings of sharing | Ring | Who sees it | How it travels | Needs internet | |---|---|---|---| | **Private** (today) | you | — | no | | **Instance library** (phase 1) | chosen teams, or everyone signed in to this instance | published in place | no | | **File or link** (phase 2) | anyone you send it to | a `.plan.json` file, or an import-from-URL box | only to fetch a URL | | **Community gallery** (phase 3) | anyone running the app | a reviewed index in the GitHub repository | yes, optional, can be switched off | ### 3.2 Data model - **`ProjectTemplate`** gains: `visibility` (`private` | `teams` | `instance`), `shared_with_teams` (M2M to `Team`), `slug`, `summary` (one line), `tags` (JSON list), `group` (business / work / hobby / other), `author_display` (`name` | `anonymous`), `published_at`, `version` (int), `version_note` ("what I learned"), `forked_from` (FK to self, nullable), `origin` (JSON: where an imported template came from), `updated_at`. - **`TemplateVersion`**: `template`, `version`, `categories`, `tasks`, `note`, `created_at`. Saving a better plan adds a version; votes, comments and the track record stay with the template (R6). - **`TemplateVote`**: `user`, `template_key` — unique together. **Keyed by the template key, not a foreign key, so built-ins can be voted and commented on like any other.** - **`TemplateComment`**: `template_key`, `author`, `body`, timestamps. Flat, newest last. - **`Project.source_template_key`** and **`source_template_version`**, set at instantiation. This is the provenance R2 needs. Nullable; existing projects simply have none. - Migrations are additive. ### 3.3 The track record (R2) Computed per template from the projects that were started from it: - **Started**, **finished** (all events at 100%), **in flight**, **abandoned** (no change in 60 days). - **How it ran:** median of (actual length ÷ planned length) across finished runs, shown as "typically runs 6% long". Only shown with **three or more** finished runs, so one project's schedule can never be read off it. - Aggregates only. It never lists which projects or whose. A project owner can opt a project out ("don't count this run") for confidential work. ### 3.4 API - `GET /api/templates/library/?q=&tag=&group=&sort=` — everything the caller may see. Sort by `proven` (finished runs, then votes; the default), `popular`, `new`, `name`. - `GET /api/templates//` — the full content, for preview. **New:** today nothing returns it. - `PATCH /api/templates//` — owner: summary, tags, group, visibility, teams, author display. - `POST …//publish/` and `…/unpublish/`; `POST …//versions/` (save a new version from a project). - `POST|DELETE …//vote/`; `GET|POST …//comments/`, `PATCH|DELETE …/comments//`. - `POST …//fork/` — a private copy to adapt, linked back by `forked_from`. - Phase 2: `GET …//export/` (the `.plan.json` file), `POST /api/templates/import/` (`file` or `url`, with `dry_run`), and optionally `GET /t/` public preview. - `instantiate` is unchanged apart from recording provenance and accepting library keys. ### 3.5 UI / UX - **A Library page** (`/templates`), linked from the dashboard beside "From Template". Cards show name, one-line summary, group and tags, author (or "a member"), length, number of tracks and events, **the track record**, votes and comment count. Search, tag chips, the sort menu. "Official" marks the built-ins. - **A template page** (`/templates/`): a read-only, zoomable drawing of the plan (reusing `ReportTimeline`): tracks, bars, dependency lines, milestone diamonds. Beside it: the description, what it assumes, version history with each "what I learned" note, the track record, **Use this template**, **Make my own copy**, **Share**, the vote button and the comments. - **Publishing is a review step, not a toggle (R7).** The dialog shows *exactly* what will be shared, every note and to-do title in one scrollable list, with three switches: include notes, include to-dos, show my name. It warns when the text contains an email address or an `@name`. Nothing is published until the author has seen that list. - **Share** offers: copy link (works for anyone who may see it), download the file, and, once it is public on an instance that allows it, the public link. - **From a project:** "Save as Template" gains "…or save as a new version of *Launch playbook*" when the project was started from one of your templates. - Works on a phone: the library is a single column of cards, the plan preview fits the width. ### 3.6 Permissions impact Decided on the server, as everywhere else ([PERMISSIONS.md](../PERMISSIONS.md)). | Action | Who | |---|---| | See a template | its owner; members of a team it is shared with; any signed-in user if `instance` | | Use, fork, vote, comment | anyone who can see it | | Edit, publish, unpublish, add a version, delete | the owner (staff may unpublish) | | Delete a comment | its author, the template's owner, or staff | - **Author names versus the people directory.** Publishing to the whole instance shows your name to people you do not share a project with, which the directory rule otherwise prevents. That is why `author_display` exists and why the publish dialog says so plainly. Default: `name` when sharing with teams, and an explicit choice when sharing with the instance. - Votes, comments and imports are throttled. Instance setting `TEMPLATE_LIBRARY` = `off` | `teams` | `instance` lets an operator cap how far sharing can go. ### 3.7 The file format (phase 2) `.plan.json`: `{ "format": "plan-template", "version": 1, "name", "summary", "description", "group", "tags", "author", "license", "categories", "tasks", "origin" }`, with `categories` and `tasks` exactly as `spec_from_project` produces them. Import treats the file as hostile, like any upload: a 1 MB cap, a strict schema (known keys, types and lengths; at most 500 events and 2,000 to-dos), dependency indexes in range and **no loops**, plain text only. Import-from-URL fetches over HTTPS only, refuses private and link-local addresses, follows no redirects off the host, and times out fast. A `dry_run` shows what will be created. Imported templates start **private**. ### 3.8 The community gallery (phase 3) - A `community-templates/` folder in the GitHub repository: one `.plan.json` per template and an `index.json` built by a GitHub Action. **Submitting a template is a pull request**; review is the moderation. In-app, "Submit to the community" downloads the file and opens a prefilled GitHub page. No server to run, and the repository gains contributors and a reason to be starred. - The app reads `index.json` over HTTPS (cached for a day), shows those templates under a **Community** tab, and imports one with a click. Operators can switch it off (`COMMUNITY_TEMPLATES=0`); air-gapped instances lose nothing else. - **The index address is a setting** (`TEMPLATE_REGISTRY_URL`, a list), not a constant. An organisation can point its instances at its own private index of house playbooks, and the file format is versioned and documented so anyone can publish one. - Community votes and discussion live on GitHub (a Discussions thread per template; 👍 reactions are counted into `index.json` by the same Action), so instances never need a GitHub token. The track record stays local: an instance only knows its own runs. - Licence: contributions are accepted under CC BY 4.0 (or CC0), stated in the folder's README. ## 4. Alternatives considered - **A hosted hub service** with accounts, votes and comments shared by all instances. The real "marketplace", and a second product to build, host, secure and moderate. Rejected for now; the GitHub-backed gallery gets most of the value for almost none of the cost. - **Star ratings (1–5) or downvotes.** More data, worse behaviour, and less useful than usage. - **Make every saved template visible to the whole instance by default.** Simplest, and a privacy accident waiting to happen: templates come from real projects. Sharing must be a deliberate act. - **Share by giving access to a template** (like project membership) instead of publishing. Familiar, but it produces a permissions matrix for templates and no library to browse. - **Skip the track record** and ship votes only. Cheaper, but it gives up the one thing no other tool has, and the one most in keeping with "the plan that worked". - **Votes and comments as foreign keys to `ProjectTemplate`.** Cleaner, but built-ins live in code and would need rows of their own. Keying by `template_key` treats both alike. ## 5. Phasing - **Phase 1 — the instance library.** Visibility (private / teams / instance), the publish review step, the Library page with preview, search, tags and sorting, votes, comments, forking, and provenance on new projects. *The track record appears as soon as projects start from templates.* - **Phase 2 — files and links** (now designed in detail in [template-library-phase2.md](template-library-phase2.md), which replaces the server-side URL fetch below with a fetch by the browser). Export, import (file and URL) with the hostile-file checks, versions with "what I learned" notes, optional public preview pages. - **Phase 3 — the community gallery.** The folder and Action in the repository, the Community tab, one-click import, the submit flow. - **Later / maybe:** following a template to hear about new versions; diffing two versions; suggesting a template from a project's name; a gallery website generated from `index.json`. ### 5.1 What phase 1 built, and where it differs from the text above Built as designed: visibility (private / teams / instance) with `TEMPLATE_LIBRARY`, the publish review step with its three switches and warnings, the Library page and a page per template with the plan drawn out, upvotes, flat comments, "make my own copy", reporting to staff, provenance on new projects, and the track record (three finished runs before a ratio is shown, per-project opt-out). The visibility rule got its test file first (`backend/projects/tests_template_library.py`). Differences, all simplifications: - **No separate `/library/` endpoint.** `GET /api/templates/` returns everything the caller may see and takes `q`, `group`, `tag`, `scope` and `sort`. The picker and the library share it. - **Publishing is a `PATCH` of `visibility`**, not a `publish/` endpoint. `unpublish/` exists because staff need it and cannot `PATCH`. - **Templates are addressed by key** (`/templates/saved:12`), not by slug. Slugs arrive with public links in phase 2. `forked_from` is stored as a key, so a copy of a built-in is recorded too. - **Notes and to-dos are held back by filtering at read time** (`share_notes`, `share_todos`), not by deleting them, so the author's own template stays whole. - **"Abandoned" is shown as "stalled"** and means unfinished with the last date more than 60 days past. Events have no modified timestamp, and this needs none. - **The planned length is stored on the project** at instantiation (`source_template_span`), so a later edit to the template cannot rewrite the history of earlier runs. - **The preview is its own small SVG** (`TemplatePreview.jsx`) rather than `ReportTimeline`, which is built around dated status facts. It is not zoomable; on a phone it scrolls sideways. - **Track records are computed per request**, one grouped query for the whole list. No cache yet. - **Not built:** versions and "what I learned" notes (phase 2, with the file format), and author pages. ### 5.2 Proposed next: a close-out, so a template knows what a run cost and how well it worked Time is the objective the schedule can measure, and phase 1 measures it. Two things that matter as much cannot be computed: **what that run cost** and **how well it worked**. Proposal: when a project that came from a template reaches 100%, offer its owner a short, optional close-out: what it cost (a number and a currency, or "skip"), how well it worked (a simple scale), and one line on what to change. The template then shows them the same way it shows time: as aggregates ("median cost $14k · 9 of 11 runs rated it worked"), only above a minimum number of runs, never per project. The one-line lesson is the natural seed for phase 2's "what I learned" version note. Cost is more sensitive than dates, so it is optional per run and follows the same opt-out. **It now has its own design document: [template-closeout.md](template-closeout.md).** ## 6. Cost & risk - **Effort:** Phase 1 **L** (model, permissions, a new page, the preview, the publish review). Phase 2 **M**. Phase 3 **M**, mostly outside the app (the Action, contribution docs, review time). - **Migration / data risk:** additive and reversible. Existing templates stay private. Existing projects have no provenance, so track records start from zero. - **Privacy risk: the main one.** People will publish templates cut from real projects. Mitigated by the review step, private-by-default, the anonymous option, aggregate-only statistics with a minimum of three runs, and a per-project opt-out. - **Moderation.** Comments and published text on an open-registration instance (the public demo) need a report button and staff removal from day one. The demo also resets every six hours. - **Security.** Phase 2 adds an upload and a server-side URL fetch (the classic route to server-side request forgery); both are specified defensively in 3.7 and need adversarial tests like `tests_authority.py`. - **Perf / UX risk:** none on the timeline. The library list must not load template bodies; previews load on demand. Track records are cached and recomputed when a project changes. - **Blast radius:** template queries change from `owner=user` to a visibility rule. That rule is the security boundary and gets its own test file before anything else is built. - **Ongoing cost:** phase 3 creates review work for the maintainer for as long as it exists. ## 7. Open questions / decisions needed - [x] **How far should sharing go first?** Teams and whole-instance, in phase 1. - [x] **Is the track record wanted?** Yes; three finished runs before a ratio is shown. - [x] **Upvotes only.** The richer signal is the track record, and later the close-out (5.2). - [x] **Names on published templates:** the author's choice each time. - [ ] **Public preview pages** with no sign-in: useful for sharing and for the demo; off by default? - [ ] **Where should the community gallery live:** a folder in this repository (proposed, brings contributors here) or a separate repository (keeps this one's history clean)? - [ ] **Licence for community templates:** CC BY 4.0 (credit required) or CC0 (no strings)? - [x] **Built-ins are votable and commentable** like everything else. - [ ] **The close-out (5.2):** is cost a plain number, or bands? Who may see a template's cost figures?