refactor: examples/ as canonical home for runnable apps - #35
Merged
Conversation
Counter is the first recipe to be folded into the new examples/<slug>/ layout. The package, template, and runtime move out of content/recipes/counter/_app/ into examples/counter/, with a new cmd/main.go subpackage that lets `go run ./examples/counter/cmd` launch the recipe standalone. Handler now takes variadic livetemplate.Option so callers control origin policy: cmd/site passes the docs-deploy allowlist; cmd/main.go passes either the same allowlist or permissive-origin + dev-mode under --dev. The recipe itself stays environment-agnostic. content/recipes/counter/index.md and content/index.md switch from `include="./_app/..."` to site-rooted `include="/examples/counter/..."`, using the tinkerdown syntax added in livetemplate/tinkerdown PR for feat/site-rooted-includes. The Dockerfile pin will need updating to that tinkerdown tag before this branch can merge — tracked in PR description. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
todos follows the same pattern as counter: the package, template, SQLite schema, and sqlc-generated bindings move from content/recipes/todos/_app/ to examples/todos/. The chromedp browser e2e test moves from e2e/todos/todos_test.go to examples/todos/ todos_test.go and switches from package main to package todos_test (blackbox external). The subprocess invocation switches from `go run .` (which assumed main was in the test cwd) to `go run ./cmd`, pointing at the new examples/todos/cmd/main.go that replaces the deleted e2e/todos/main.go wrapper. internal `github.com/livetemplate/docs/content/recipes/todos/_app/db` import paths in controller.go, state.go, db_manager.go rewrite to `examples/todos/db`. go.mod promotes chromedp/chromedp and gorilla/websocket from indirect to direct (now used by the relocated test). content/recipes/todos/index.md switches all seven literate includes from `./_app/...` to site-rooted `/examples/todos/...`. Handler signature unchanged — todos already took variadic livetemplate.Option, so cmd/site keeps passing WithAllowedOrigins(allowedOrigins) the same way. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
patterns is the third example folded into examples/<slug>/. All 17 Go source files, the templates/ subdirectory (5 categories × ~6 templates each), and the chromedp e2e test move from content/recipes/patterns/_app/ and e2e/patterns/ to examples/patterns/. The new examples/patterns/cmd/main.go preserves the dual-mount the old e2e/patterns/main.go required: tests navigate to bare root for the catalog index AND to "/recipes/ui-patterns/<cat>/<slug>" for individual pattern pages. patterns.Handler is idempotent across calls with the same basePath (handlerOnce cache), so both mounts route to the same logical app. content/recipes/broadcasting.md updates 4 region-based includes from `./patterns/_app/...` to `/examples/patterns/...`. ui-patterns/index.md updates 2 cross-references (link + tree URL) to docs/examples/patterns. Handler signature unchanged — already took (basePath, opts...). The //go:embed templates directive resolves to examples/patterns/templates post-move (path is relative to the package file). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
progressive-enhancement is the fourth example folded into examples/<slug>/. controller.go, handler.go, the .tmpl, and the e2e test all move from content/recipes/progressive-enhancement/_app/ + e2e/progressive-enhancement/ to examples/progressive-enhancement/. The new examples/progressive-enhancement/cmd/main.go preserves the dual-mount the old e2e wrapper required: Tier A (default JS+WS) at "/", Tier B (WebSocket-disabled fallback) at "/no-ws/". cmd/site already mounts the production /apps/progressive-enhancement/ + /no-ws/ dual the same way — those mounts are unchanged here aside from the new import path. content/recipes/progressive-enhancement/index.md updates 4 includes from `./_app/...` to site-rooted `/examples/progressive-enhancement/...`. Test moves package main → package progressiveenhancement_test (blackbox); subprocess invocation switches from `go run .` to `go run ./cmd`. go.mod promotes chromedp/cdproto to direct (now used by the relocated test). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
login is the fifth example folded into examples/<slug>/. controller.go, handler.go, auth.html, and the chromedp e2e test move from content/recipes/login/_app/ + e2e/login/ to examples/login/. The new examples/login/cmd/main.go preserves the MOUNT_PATH-driven dual-mount the old e2e wrapper required: root mount for happy-path tests, subpath StripPrefix mount for redirect-target-bug regression tests. content/recipes/login/index.md updates 6 region-based includes from `./_app/...` to site-rooted `/examples/login/...`. Test moves package main → package loginrecipe_test (blackbox); imports github.com/livetemplate/docs/examples/login (package name remains loginrecipe). Subprocess invocation switches `go run .` → `go run ./cmd`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shared-notepad is the sixth and final in-tree example folded into examples/<slug>/. controller.go, handler.go, notepad.tmpl, and the chromedp e2e test move from content/recipes/shared-notepad/_app/ + e2e/shared-notepad/ to examples/shared-notepad/. examples/shared-notepad/cmd/main.go uses notepad.NewDemoBasicAuth so tests can authenticate as alice/bob via the standard http://<user>:demo@host/... URL form. Production cmd/site still wires AnonymousAuthenticator because tinkerdown's embed-lvt server-side prefetch can't forward Authorization headers in inline embeds — that mount is unchanged aside from the new import path. content/recipes/shared-notepad/index.md updates 6 region-based includes from `./_app/...` to site-rooted `/examples/shared-notepad/...`. Test moves package main → package notepad_test (blackbox); imports github.com/livetemplate/docs/examples/shared-notepad (package name is notepad). Subprocess invocation switches `go run .` → `go run ./cmd`. All six in-tree example folders are now under examples/. Build is clean, go test -short ./examples/... passes (chromedp tests skip under -short but compile correctly). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With all per-example e2e tests now co-located in examples/<slug>/, the e2e/ directory contains only site-level tests (landing, broadcasting embed, responsive, staging) plus the screenshot/sweep CLI tools. There is no longer a reason for these to be in a separate Go module — the original split existed so each e2e/<slug>/ could have its own main.go wrapper without producing duplicate-main-package errors in the parent module. Drop e2e/go.mod and e2e/go.sum. All deps the e2e tests + cmd binaries need are already in the root go.mod (promoted to direct in earlier commits during per-example test migration). go test ./e2e/... + go build ./... + go test ./examples/... all clean post-dissolve. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ntries With docs/examples/<slug>/ as the canonical home for runnable apps (no longer mirrored from livetemplate/examples), the sync tool's mirrorAdjacentApp function and its 4 unit tests become dead code: - Delete mirrorAdjacentApp + copyFile from cmd/sync/sync.go - Delete the 4 _app/-mirroring tests from cmd/sync/sync_test.go - Drop the 9 livetemplate/examples entries from source-of-truth.yaml (mirrored README summaries that now live native at content/recipes/ apps/*.md, plus the contributing/examples mirror) - Drop livetemplate/examples from the sync workflow's source_repo choice list (.github/workflows/sync.yml) - Update content/_meta/source-of-truth.md prose: replace the "_app/ adjacency rule" section with a "site-rooted includes for cross-tree literate authoring" section, drop the livetemplate/examples link-rewrite rules This isolates the docs site from the livetemplate/examples repo. The orphan examples (chat, avatar-upload, flash-messages, etc.) will be ported into docs/examples/ in a follow-up commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CONTRIBUTING.md: - "Where content lives" lists the new examples/<slug>/ as the canonical home for runnable apps; content/recipes/ is now markdown-only. - New "Adding an example" section documents the per-app shape (handler + tmpl + test + cmd/main.go) and the 4-step add procedure. - Local-dev section adds `go run ./examples/<slug>/cmd --dev` and `make serve`; updates test commands to `go test ./examples/...` (replacing the obsolete `cd e2e && go test ./...`). README.md: - "How content gets here" table drops the livetemplate/examples row (no longer a source of truth); adds the examples/<slug>/ row. - Adds a paragraph describing the runnable-apps shape. Makefile: - Drop `run-firstapp` (cited an _app/ path that no longer exists). - Add `site` (run cmd/site standalone), `examples-test` (= go test ./examples/...), `examples-list` (enumerate examples one per line). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the remaining standalone-only examples into the docs repo as the canonical home. Each was a single-file Go program in livetemplate/examples/<slug>/; we keep that shape (package main + *.tmpl + *_test.go in one directory) because these examples are standalone-only — they don't get embedded inline in docs pages, so they don't need the Handler-package + cmd/main.go split the in-tree recipes use. Folders ported (file count, deploy notes): - chat (main.go + chat.tmpl + chat_e2e_test.go + README) - avatar-upload (main.go + avatar-upload.tmpl + test + run.sh + uploads/.gitkeep; cleared the runtime uploads/* artifacts that shouldn't be in version control) - flash-messages (main.go + flash.tmpl + two test files + README) - dialog-patterns (main.go + dialog-patterns.tmpl + test) - landing-demo (main.go + counter.tmpl + test + README + Dockerfile + fly.toml — preserved so the lt-landing-demo fly app can keep deploying. After livetemplate/examples is archived in PR G, the Dockerfile will need updating to clone from docs.) - live-preview (main.go + preview.tmpl + test) Version skew check: zero. livetemplate/examples/go.mod and docs/go.mod both pin livetemplate v0.9.2; no API surface to reconcile. All 6 packages compile + pass tests under -short (go test ./examples/... runs all 12 example packages green). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The examples/<slug>/ refactor relies on tinkerdown's new site-rooted include syntax (`include="/examples/foo/bar.go"`), which is in livetemplate/tinkerdown PR feat/site-rooted-includes — not yet merged or tagged. The Dockerfile clones that branch directly so docs CI's `tinkerdown validate content/` step succeeds. Once PR 0 in livetemplate/tinkerdown merges and is tagged (e.g. v0.3.0), revert this branch pin back to a tag in the same `git clone --branch=` form. The TODO is in the Dockerfile comment above the ARG line. This commit is the last piece of PR 1 (the docs refactor). After this: - docs CI build is green (tinkerdown branch supports the new syntax) - go build ./... + go test ./examples/... clean - all 55 content pages validate with the new tinkerdown Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
livetemplate/tinkerdown#280 has merged and v0.3.0 is published, which includes the site-rooted include="/..." syntax this PR depends on. Replace the temporary feat/site-rooted-includes branch pin with the proper version tag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 23, 2026
Merged
adnaan
added a commit
that referenced
this pull request
May 23, 2026
) The cross-repo cross-repo-test.yml's new test-docs-examples job (and local `go test -run` invocations under e2etest.StartTestServer) shells out to `go run ./cmd` without passing flags. The cmd/main.go entry points only enabled dev mode via the `--dev` flag, so the spawned server got the production origin allowlist — which rejects Chrome's `http://host.docker.internal:<port>/` WebSocket upgrades: WARN WebSocket origin rejected origin=http://host.docker.internal:37823 ERROR WebSocket upgrade failed component=live_handler error="websocket: request origin not allowed by Upgrader.CheckOrigin" Result: TestLogin_E2E/ServerPushedWelcome, all patterns realtime tests, TestPE_TierA_BrowserE2E, TestSharedNotepad_E2E, TestTodosE2E all failed in the cross-repo job (~30+ test FAILures total). Fix: honor LVT_DEV_MODE=true in addition to the --dev flag. e2etest. StartTestServer already sets `LVT_DEV_MODE=true` in the subprocess environment (see lvt/testing/chrome.go), so the production allowlist branch is correctly bypassed when the test framework invokes cmd/main. Applied to all 6 in-tree examples (counter, todos, patterns, progressive-enhancement, login, shared-notepad). This bug existed in PR #35 from the start — I missed it locally because `go test -short ./examples/...` skips chromedp browser tests. The cross-repo CI ran the full suite (no -short) and surfaced it. Verified fix locally with `go test -run TestLogin_E2E$ ./examples/login/`: TestLogin_E2E/ServerPushedWelcome now PASSES. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 tasks
adnaan
added a commit
to livetemplate/livetemplate
that referenced
this pull request
May 23, 2026
The test-examples job ran the livetemplate/examples monorepo's test suite against this repo's main. With the docs examples/-as-canonical refactor (livetemplate/docs#35) absorbing every runnable app into docs/examples/<slug>/, livetemplate/examples is redundant — every example it tested is now covered by test-docs-examples (added in #437). Drop the job (~70 lines of YAML). Three jobs now remain in cross-repo-test.yml: test-lvt, test-docs-examples, test-tinkerdown. After this lands, livetemplate/examples can be archived as a GitHub repo setting — no remaining workflows reference it. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidates every runnable app under
docs/examples/<slug>/(Go package, template, e2e test, standalone runner). Recipe markdown stays incontent/recipes/<slug>/index.mdas docs-only.Before: each example was spread across
content/recipes/<slug>/_app/(the package),e2e/<slug>/main.go + *_test.go(separate Go module wrapper),content/recipes/apps/<slug>.md(synced README fromlivetemplate/examples). Adding a recipe touched 4 files across 2 repos, including a per-recipe job inlivetemplate/livetemplate/.github/workflows/cross-repo-test.yml.After: every runnable app lives at
examples/<slug>/. Adding a recipe ismkdir examples/foo+ write the four files (handler, tmpl, test, cmd/main.go) + 2 lines incmd/site/main.go. Thelivetemplate/examplesrepo becomes redundant (cleanup deferred to follow-up PR G).Built on top of livetemplate/tinkerdown#280 which adds site-rooted
include="/..."syntax so recipe markdown can cite the relocated source files cleanly.Commits
1af94d0content/recipes/_app/d5bc795f76b8a7b1edf8265886803041798f2fd0ace2e/go.modinto docs root moduleca289ea_app/adjacency mirror +livetemplate/examplesentries (-269 lines)8fbbd71examples/layout7b585c1livetemplate/examples(chat, avatar-upload, flash-messages, dialog-patterns, landing-demo, live-preview)842226afeat/site-rooted-includesbranch (until tinkerdown#280 ships a tag)Result
examples/(6 in-tree refactored, 6 orphan-ported)./_app/...to site-rooted/examples/<slug>/...Blockers / follow-ups
feat/site-rooted-includes(currently a branch pin, marked TEMPORARY in the Dockerfile)..github/workflows/cross-repo-test.ymlinto onetest-docs-examplesjob that runsgo test ./examples/.... Saves ~280 lines of YAML.Test plan
go build ./...cleango test -short ./examples/...— all 12 example packages greengo test -short ./cmd/...— sync tool + site main build greentinkerdown validate content/(with tinkerdown#280 binary) — 55/55 pages validDockerfileTINKERDOWN_REFback to a tag and verify CI is fully greengo test ./examples/...without-short) — passes locally; runs in CI per the cross-repo workflow🤖 Generated with Claude Code