You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Blocked by #137 — the GIFs this page embeds are produced there.
Blocked by #146 — the Docker path this page teaches ships there (ghcr.io/specsnl/specs-cli,
its runtime contract, and the Docker section of the Installation page the tutorial links to).
The observation
The docs site teaches Specs entirely in reference pieces: Installation, Quick Start, Commands, Template Structure, The project file, Template Functions, Storage, Architecture.
Quick Start is the only narrative page, and it is four commands against a template that does not exist:
specs use specsnl/my-template ./my-project
It stops at the invocation. It never shows a prompt, a computed value, a conditional path, or a hook — which is to say it never shows what the tool does. And specsnl/specs-laravel-project — a real template exercising nearly every feature the reference pages document — is not referenced anywhere on the site.
The gap: someone who runs brew install specsnl/tap/specs has no path from there to a working project. The same is true of someone who never installs specs at all and reaches for docker run ghcr.io/specsnl/specs-cli — a first-class way to use the tool once #146 lands, and the one where getting it wrong is easiest, because the scaffold has to land on the host filesystem with the caller's ownership.
Proposal
A Tutorials section, whose first entry walks from a fresh machine to a booted Laravel app and on to reusing the template from the registry — shown for both installation routes: Homebrew and the official Docker image. It is the page that ties the reference docs together; each section links out to them rather than restating them.
docs/content/docs/tutorials/_index.md is a Hextra section index with a {{< cards >}} list, the same shape as commands/_index.md, so later tutorials have a home. laravel-project.md is the first card.
Two paths, one page
The reader picks a route once, at the install step, and the page carries that choice through every specs invocation. Where a step differs, show both with Hextra's {{< tabs items="Homebrew,Docker" >}} — one tab per route, Homebrew first, in this order on every tab set so the reader is never hunting. No shortcode on the site uses tabs yet, so confirm it renders under task docs:build before leaning on it; a pair of labelled fenced blocks is the fallback.
The split is narrower than it looks. Only the specs invocations differ — sections 3, 4 and 7. Section 6 rejoins: the generated project boots with task and Docker on the host either way, because the container scaffolded onto the host filesystem and then exited. Say that explicitly at the top of section 6, or a Docker reader will expect to still be inside a container.
The page
Prerequisites — Docker/OrbStack and task for both routes; Homebrew only for that one. Link Installation rather than restating the install matrix; mention specs@rc by link only.
Install — the fork in the road, and the only place the reader makes a choice:
Homebrew — brew install specsnl/tap/specs, then specs --version.
Docker — nothing to install. docker run --rm ghcr.io/specsnl/specs-cli --version is the equivalent smoke test; the entrypoint is specs itself, so everything after the image name is the command line the Homebrew reader types. Pin to an exact version tag in anything durable; link the tag table on Installation rather than repeating it.
For the Docker route, introduce the invocation the rest of the page reuses — once, here, so later sections can stay short:
Three things earn a sentence each, because each one is a silent failure when omitted: --user so the scaffolded files belong to the reader and not to uid 1000; HOME because an overridden uid owns no home directory in the image; -it because specs only prompts when stdin is a terminal, and without one it errors rather than quietly taking defaults. Link the Docker section of Installation for the full contract instead of restating it.
The Docker tab is the same command line after the image name, with ./acme landing in the bind-mounted $PWD. The GIFs record the Homebrew run — one recording, referenced from the shared part of the section, not duplicated per tab.
This template defines post-use hooks and is fetched remotely, so specs prints them and asks before running. Both routes get the prompt; the Docker reader gets it only because of -it. Worth a line here, since the payoff in section 5 is partly what those hooks did.
The prompts, one by one — a table of every variable the template's project.yml declares, each with what it controls downstream: ProjectName, ProjectShortName, ProjectDescription, PhpVersion (8.5 / 8.4 / 8.3), RepoName, ComposerLicense, IssuePrefix, Database (postgres / mariadb / mysql), AddBugSnag, AddFilament, AddE2E.
Identical on both routes — the prompt flow is the binary's, not the shell's.
What the answers changed — the payoff section, and where the tutorial earns its place next to the reference docs:
What the reader sees
Reference page
DbConnection, DbPort, DbOrbStackDomain, PhpDockerTag derived from two answers
Computed values
[[ if .AddE2E ]]e2e[[ end ]] and the per-database directories appearing or not
Template engine
The [[`.gitignore`]] verbatim escape
Template engine
__delimiters — why this template writes [[ … ]] and not {{ … }}
Template structure
The four post-use hooks: the project arrives with a .env and an initialised git repo
Template engine
Both routes land on the same tree. If the Docker route does not — file ownership, a missing .env, an un-initialised git repo — that is a bug in the image contract, not something the tutorial papers over.
Boot it — the task commands from the generated project, ending at a page in the browser. Open by stating that the routes have converged: the container is gone, ./acme is an ordinary directory, and everything from here runs on the host. Verify these against the template's own Taskfile.dist.yml while writing; do not invent task names.
The Docker tab needs one addition the earlier sections did not: the registry lives under /config, which dies with the container, so these commands take --volume specs-config:/config as well. That is the whole reason this section has two tabs — and it is the one place where a Docker reader following the section-3 invocation would get a confusing result (a template list that is empty every time).
Next steps — cards to Commands, Template Structure, The project file, and the Docker section of Installation for readers who want the CI-shaped, non-interactive form (--use-defaults, --arg, --yes).
Conventions the page must follow
Delimiter spacing — {{ .Variable }}, and [[ .Variable ]] for this template, never {{.Variable}}. Pipe form for functions — {{ .Name | toKebabCase }}. Run the markdown table formatter over the finished pages.
Scope
docs/content/docs/tutorials/_index.md — title: Tutorials, weight: 3, prev: /docs/quick-start, next: /docs/tutorials/laravel-project, a {{< cards >}} list
docs/content/docs/tutorials/laravel-project.md — the walkthrough, embedding both GIFs, with Homebrew/Docker tabs on the steps that differ
Reweight so the sidebar stays in order: commands/_index.md 3 -> 4, template-structure.md 4 -> 5, project-yaml.md 5 -> 6, template-functions.md 6 -> 7, storage.md 7 -> 8 (architecture stays 10)
Repair the prev/next chain: quick-start.mdnext -> /docs/tutorials; commands/_index.mdprev -> /docs/tutorials/laravel-project
README.md — link the tutorial from the getting-started area
task md:fix-tables, then read the diff
Done when
A reader following the page top to bottom on a clean machine reaches a running Laravel app — by either route
Every command shown was actually executed while writing the page, and its real output is what appears — no invented prompts, task names, or paths. The Docker commands were run against the published image (or the locally built --target debian one before the first tag exists), not adapted on paper from the Homebrew ones
The scaffold produced by the Docker route is owned by the caller and byte-identical to the Homebrew one
Every variable in the template's project.yml is accounted for on the page
The sidebar reads Installation -> Quick Start -> Tutorials -> Commands -> … with no duplicate weights, and task docs:build is clean
The page links out to the reference docs rather than duplicating them — in particular it does not restate the Docker runtime contract that Installation owns
Notes
Docs-only: no Go code changes, so the tests/docs/README triad in AGENTS.md reduces to docs + README.
Nothing in specsnl/specs-laravel-project needs to change for this. If writing the tutorial turns up a defect there, file it in that repository.
The Docker half cannot be written honestly before #146 merges and a tag publishes ghcr.io/specsnl/specs-cli. Either wait for it, or write the Homebrew half first and add the tabs in a follow-up commit on the same branch.
The observation
The docs site teaches Specs entirely in reference pieces: Installation, Quick Start, Commands, Template Structure, The project file, Template Functions, Storage, Architecture.
Quick Start is the only narrative page, and it is four commands against a template that does not exist:
It stops at the invocation. It never shows a prompt, a computed value, a conditional path, or a hook — which is to say it never shows what the tool does. And
specsnl/specs-laravel-project— a real template exercising nearly every feature the reference pages document — is not referenced anywhere on the site.The gap: someone who runs
brew install specsnl/tap/specshas no path from there to a working project. The same is true of someone who never installsspecsat all and reaches fordocker run ghcr.io/specsnl/specs-cli— a first-class way to use the tool once #146 lands, and the one where getting it wrong is easiest, because the scaffold has to land on the host filesystem with the caller's ownership.Proposal
A Tutorials section, whose first entry walks from a fresh machine to a booted Laravel app and on to reusing the template from the registry — shown for both installation routes: Homebrew and the official Docker image. It is the page that ties the reference docs together; each section links out to them rather than restating them.
docs/content/docs/tutorials/_index.mdis a Hextra section index with a{{< cards >}}list, the same shape ascommands/_index.md, so later tutorials have a home.laravel-project.mdis the first card.Two paths, one page
The reader picks a route once, at the install step, and the page carries that choice through every
specsinvocation. Where a step differs, show both with Hextra's{{< tabs items="Homebrew,Docker" >}}— one tab per route, Homebrew first, in this order on every tab set so the reader is never hunting. No shortcode on the site usestabsyet, so confirm it renders undertask docs:buildbefore leaning on it; a pair of labelled fenced blocks is the fallback.The split is narrower than it looks. Only the
specsinvocations differ — sections 3, 4 and 7. Section 6 rejoins: the generated project boots withtaskand Docker on the host either way, because the container scaffolded onto the host filesystem and then exited. Say that explicitly at the top of section 6, or a Docker reader will expect to still be inside a container.The page
Prerequisites — Docker/OrbStack and
taskfor both routes; Homebrew only for that one. Link Installation rather than restating the install matrix; mentionspecs@rcby link only.Install — the fork in the road, and the only place the reader makes a choice:
brew install specsnl/tap/specs, thenspecs --version.docker run --rm ghcr.io/specsnl/specs-cli --versionis the equivalent smoke test; the entrypoint isspecsitself, so everything after the image name is the command line the Homebrew reader types. Pin to an exact version tag in anything durable; link the tag table on Installation rather than repeating it.For the Docker route, introduce the invocation the rest of the page reuses — once, here, so later sections can stay short:
Three things earn a sentence each, because each one is a silent failure when omitted:
--userso the scaffolded files belong to the reader and not to uid 1000;HOMEbecause an overridden uid owns no home directory in the image;-itbecausespecsonly prompts when stdin is a terminal, and without one it errors rather than quietly taking defaults. Link the Docker section of Installation for the full contract instead of restating it.Scaffold —
specs use specsnl/specs-laravel-project ./acme, withuse.giffrom docs: record the CLI with VHS — a pinned recording container, tapes in-repo, and a demo:record task #137 right here. Bareowner/repois the current form (see 129e9f6); note that the oldergithub:prefix in the template's own README still works.The Docker tab is the same command line after the image name, with
./acmelanding in the bind-mounted$PWD. The GIFs record the Homebrew run — one recording, referenced from the shared part of the section, not duplicated per tab.This template defines
post-usehooks and is fetched remotely, sospecsprints them and asks before running. Both routes get the prompt; the Docker reader gets it only because of-it. Worth a line here, since the payoff in section 5 is partly what those hooks did.The prompts, one by one — a table of every variable the template's
project.ymldeclares, each with what it controls downstream:ProjectName,ProjectShortName,ProjectDescription,PhpVersion(8.5 / 8.4 / 8.3),RepoName,ComposerLicense,IssuePrefix,Database(postgres / mariadb / mysql),AddBugSnag,AddFilament,AddE2E.Identical on both routes — the prompt flow is the binary's, not the shell's.
What the answers changed — the payoff section, and where the tutorial earns its place next to the reference docs:
DbConnection,DbPort,DbOrbStackDomain,PhpDockerTagderived from two answers[[ if .AddE2E ]]e2e[[ end ]]and the per-database directories appearing or not[[`.gitignore`]]verbatim escape__delimiters— why this template writes[[ … ]]and not{{ … }}post-usehooks: the project arrives with a.envand an initialised git repoBoth routes land on the same tree. If the Docker route does not — file ownership, a missing
.env, an un-initialised git repo — that is a bug in the image contract, not something the tutorial papers over.Boot it — the
taskcommands from the generated project, ending at a page in the browser. Open by stating that the routes have converged: the container is gone,./acmeis an ordinary directory, and everything from here runs on the host. Verify these against the template's ownTaskfile.dist.ymlwhile writing; do not invent task names.Reuse it from the registry —
specs template download specsnl/specs-laravel-project laravel, thenlist,use,update, withtemplate.giffrom docs: record the CLI with VHS — a pinned recording container, tapes in-repo, and a demo:record task #137; link Storage for where the registry puts it.The Docker tab needs one addition the earlier sections did not: the registry lives under
/config, which dies with the container, so these commands take--volume specs-config:/configas well. That is the whole reason this section has two tabs — and it is the one place where a Docker reader following the section-3 invocation would get a confusing result (atemplate listthat is empty every time).Next steps — cards to Commands, Template Structure, The project file, and the Docker section of Installation for readers who want the CI-shaped, non-interactive form (
--use-defaults,--arg,--yes).Conventions the page must follow
Delimiter spacing —
{{ .Variable }}, and[[ .Variable ]]for this template, never{{.Variable}}. Pipe form for functions —{{ .Name | toKebabCase }}. Run the markdown table formatter over the finished pages.Scope
docs/content/docs/tutorials/_index.md—title: Tutorials,weight: 3,prev: /docs/quick-start,next: /docs/tutorials/laravel-project, a{{< cards >}}listdocs/content/docs/tutorials/laravel-project.md— the walkthrough, embedding both GIFs, with Homebrew/Docker tabs on the steps that differcommands/_index.md3 -> 4,template-structure.md4 -> 5,project-yaml.md5 -> 6,template-functions.md6 -> 7,storage.md7 -> 8 (architecture stays 10)prev/nextchain:quick-start.mdnext->/docs/tutorials;commands/_index.mdprev->/docs/tutorials/laravel-projectREADME.md— link the tutorial from the getting-started areatask md:fix-tables, then read the diffDone when
--target debianone before the first tag exists), not adapted on paper from the Homebrew onesproject.ymlis accounted for on the pagetask docs:buildis cleanNotes
Docs-only: no Go code changes, so the tests/docs/README triad in
AGENTS.mdreduces to docs + README.Nothing in
specsnl/specs-laravel-projectneeds to change for this. If writing the tutorial turns up a defect there, file it in that repository.The Docker half cannot be written honestly before #146 merges and a tag publishes
ghcr.io/specsnl/specs-cli. Either wait for it, or write the Homebrew half first and add the tabs in a follow-up commit on the same branch.