From 18d431df912ac4fd66f74a2b3b348c0b3d410db5 Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Sat, 1 Aug 2026 15:51:27 +0000 Subject: [PATCH 1/6] fix(sync): resolve upstream-relative links instead of shipping them dead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every mirrored page carried links that 404 on the docs site. Upstream links its siblings relatively — ../references/api-reference.md from docs/guides/foo.md — which is correct in that repo and meaningless once mirrored, because the site has no such path. The existing rewriter only mapped absolute GitHub URLs, so 55 relative links across 16 synced pages shipped broken, and could not be fixed in content/ because the next sync overwrites it. The fix belongs in the mirror step. RewriteRelative resolves each target against the page's own source_path directory, which handles ./, ../ and ../../ uniformly, then either maps the result to a mirrored page's site URL or — when the file is real upstream but not mirrored (ROADMAP.md, docs/proposals/*, docs/design/*) — rewrites to its GitHub URL at the synced ref, so the reader still reaches it. Fragments are preserved; directory targets use /tree/ rather than /blob/. Deliberately conservative in three places. It matches only the ](...) form, since upstream prose contains bare relative paths that are not links. It skips fenced code blocks, because a relative path inside an example is part of the example — this is the regression the existing blunt ReplaceAll would have caused, and it is pinned by a test. And it never matches across repos: two repos can hold the same source_path, and "upstream meant a file in its own repo that does not exist" is indistinguishable from "upstream meant the other repo's file", so guessing would invent links rather than fix them. Kept as a separate pass rather than folded into Rewrite: the two have different matching rules, so the existing fence-preservation guarantees stay intact and independently tested. Verified by running the real sync at v0.22.0 / v0.2.0 / v0.20.0: relative links in synced pages went 55 -> 0, the diff contains nothing but link edits, all 16 site-URL targets resolve to real pages, and 9 of 12 GitHub fallbacks return 200. The other 3 are pre-existing upstream authoring bugs this surfaces rather than causes — they were equally dead before, just silently: - guides/OBSERVABILITY.md links ../internal/observe/, but that package is at the repo root, so it is one ../ short. - the lvt CLI guide links ../references/{api-reference,template-support-matrix}.md, which exist in livetemplate, not lvt. Filed for upstream rather than papered over here. Also fixes 3 dead links in docs-native pages left from the examples-repo mirror era (counter.md pointing at ../../docs/CONFIGURATION.md, chat.md at ../../README.md and a design doc); their front matter still claims livetemplate/examples, which M2-P3 reconciles. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QzC2djPjPHkNJgPzFpMX7v --- cmd/sync/sync.go | 91 +++++++++++++- cmd/sync/sync_test.go | 130 ++++++++++++++++++++ content/cli/index.md | 6 +- content/guides/observability.md | 2 +- content/guides/progressive-complexity.md | 20 +-- content/guides/scaling.md | 2 +- content/guides/standard-html-reactivity.md | 18 +-- content/recipes/apps/chat.md | 5 +- content/recipes/apps/counter.md | 2 +- content/reference/api.md | 4 +- content/reference/authentication.md | 2 +- content/reference/client-attributes.md | 8 +- content/reference/configuration.md | 6 +- content/reference/controller-pattern.md | 2 +- content/reference/limitations.md | 14 +-- content/reference/navigate.md | 4 +- content/reference/progressive-complexity.md | 2 +- content/reference/pubsub.md | 8 +- content/reference/server-actions.md | 8 +- content/reference/session.md | 2 +- 20 files changed, 274 insertions(+), 62 deletions(-) diff --git a/cmd/sync/sync.go b/cmd/sync/sync.go index 66fc687..a470852 100644 --- a/cmd/sync/sync.go +++ b/cmd/sync/sync.go @@ -6,6 +6,7 @@ import ( "io" "os" "os/exec" + "path" "path/filepath" "regexp" "strings" @@ -114,6 +115,7 @@ func Run(opts Options) (Result, error) { continue } rewritten := rewriter.Rewrite(stripped) + rewritten = rewriter.RewriteRelative(rewritten, p, opts.Ref) out := composeWithFrontmatter(title, p.SourceRepo, p.SourcePath, opts.Ref, commit, upstreamFM, rewritten) existing, _ := os.ReadFile(dest) @@ -358,21 +360,100 @@ func writeFrontmatterValue(b *strings.Builder, key string, v any) { // known page are left untouched (so external GitHub references survive). type linkRewriter struct { urlToSiteURL map[string]string + // repoPathToSiteURL maps repo+"\x00"+source_path to a page's site URL. + // RewriteRelative needs the lookup keyed by repo-relative path rather + // than by full GitHub URL, since that is what resolving an + // upstream-relative link produces. + repoPathToSiteURL map[string]string } func newLinkRewriter(cfg *SourceOfTruth) *linkRewriter { m := make(map[string]string, len(cfg.Pages)*2) + byPath := make(map[string]string, len(cfg.Pages)) for _, p := range cfg.Pages { repo := strings.TrimSuffix(strings.TrimSpace(p.SourceRepo), "/") - path := strings.TrimPrefix(strings.TrimSpace(p.SourcePath), "/") - if repo == "" || path == "" { + srcPath := strings.TrimPrefix(strings.TrimSpace(p.SourcePath), "/") + if repo == "" || srcPath == "" { continue } // Both the canonical edit-form URL and the blob form should rewrite. - m[repo+"/blob/main/"+path] = p.SiteURL - m[repo+"/edit/main/"+path] = p.SiteURL + m[repo+"/blob/main/"+srcPath] = p.SiteURL + m[repo+"/edit/main/"+srcPath] = p.SiteURL + byPath[repo+"\x00"+srcPath] = p.SiteURL } - return &linkRewriter{urlToSiteURL: m} + return &linkRewriter{urlToSiteURL: m, repoPathToSiteURL: byPath} +} + +// relativeLinkRE matches a markdown link whose target is upstream-relative +// ("./x.md", "../references/y.md"). Only the `](...)` form is matched: +// upstream prose also contains bare relative paths that are not links, and +// rewriting those would corrupt them. +var relativeLinkRE = regexp.MustCompile(`\]\((\.[^)\s]*)\)`) + +// RewriteRelative resolves upstream-relative markdown links against the +// mirrored page's own location in its source repo. +// +// Upstream links its siblings relatively — `../references/api-reference.md` +// from `docs/guides/foo.md` — which is correct *in that repo* and dead once +// mirrored, because the docs site has no such path. Resolving against +// page.SourcePath's directory yields the repo-relative target, which either +// maps to a mirrored page (rewrite to its site URL) or is a real upstream +// file that is not mirrored (rewrite to its GitHub URL, so the reader still +// reaches it rather than a 404). +// +// Fenced code blocks are skipped: a relative path inside an example is part +// of the example. +func (r *linkRewriter) RewriteRelative(body string, page PageEntry, ref string) string { + repo := strings.TrimSuffix(strings.TrimSpace(page.SourceRepo), "/") + srcPath := strings.TrimPrefix(strings.TrimSpace(page.SourcePath), "/") + if repo == "" || srcPath == "" || ref == "" { + return body + } + srcDir := path.Dir(srcPath) + + lines := strings.Split(body, "\n") + inFence := false + for i, ln := range lines { + if strings.HasPrefix(strings.TrimSpace(ln), "```") { + inFence = !inFence + continue + } + if inFence { + continue + } + lines[i] = relativeLinkRE.ReplaceAllStringFunc(ln, func(m string) string { + target := relativeLinkRE.FindStringSubmatch(m)[1] + return "](" + r.resolveRelative(target, repo, srcDir, ref) + ")" + }) + } + return strings.Join(lines, "\n") +} + +// resolveRelative maps one upstream-relative link target to its docs-site or +// GitHub destination, preserving any #fragment. +func (r *linkRewriter) resolveRelative(target, repo, srcDir, ref string) string { + frag := "" + if i := strings.IndexByte(target, '#'); i >= 0 { + frag, target = target[i:], target[:i] + } + if target == "" { + return target + frag + } + isDir := strings.HasSuffix(target, "/") + resolved := path.Join(srcDir, target) + // A target that climbs above the repo root cannot be resolved to + // anything meaningful; leave it exactly as it was. + if resolved == ".." || strings.HasPrefix(resolved, "../") { + return target + frag + } + if site, ok := r.repoPathToSiteURL[repo+"\x00"+resolved]; ok { + return site + frag + } + kind := "blob" + if isDir { + kind = "tree" + } + return repo + "/" + kind + "/" + ref + "/" + resolved + frag } // Rewrite applies the rewrite rules to the input markdown body. Only diff --git a/cmd/sync/sync_test.go b/cmd/sync/sync_test.go index 819efed..4379952 100644 --- a/cmd/sync/sync_test.go +++ b/cmd/sync/sync_test.go @@ -355,3 +355,133 @@ func TestLinkRewriter_AlsoHandlesEditURLs(t *testing.T) { t.Errorf("edit URL not rewritten: %q", got) } } + +// relCfg is the fixture for the RewriteRelative tests: two mirrored pages in +// the livetemplate repo (one under docs/guides, one under docs/references) +// plus one in lvt, so same-repo resolution can be told apart from cross-repo. +func relCfg() *SourceOfTruth { + return &SourceOfTruth{ + Pages: []PageEntry{ + {SiteURL: "/guides/scaling", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/guides/SCALING.md"}, + {SiteURL: "/reference/api", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/references/api-reference.md"}, + {SiteURL: "/cli/auth-customization", SourceRepo: "https://github.com/livetemplate/lvt", SourcePath: "docs/guides/auth-customization.md"}, + }, + } +} + +func TestRewriteRelative_MappedSiblingBecomesSiteURL(t *testing.T) { + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/guides/scaling", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/guides/SCALING.md"} + + body := "See [the API](../references/api-reference.md) for details." + got := r.RewriteRelative(body, page, "v0.22.0") + + if !strings.Contains(got, "[the API](/reference/api)") { + t.Errorf("sibling not rewritten to site URL: %q", got) + } +} + +func TestRewriteRelative_PreservesFragment(t *testing.T) { + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/guides/scaling", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/guides/SCALING.md"} + + body := "See [Async](../references/api-reference.md#async)." + got := r.RewriteRelative(body, page, "v0.22.0") + + if !strings.Contains(got, "[Async](/reference/api#async)") { + t.Errorf("fragment dropped or link not rewritten: %q", got) + } +} + +func TestRewriteRelative_DotSlashResolvesWithinSameDir(t *testing.T) { + // content/cli/index.md links a sibling as ./auth-customization.md; it must + // resolve against the page's own directory, not the repo root. + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/cli/", SourceRepo: "https://github.com/livetemplate/lvt", SourcePath: "docs/guides/lvt-cli-guide.md"} + + body := "See [auth](./auth-customization.md)." + got := r.RewriteRelative(body, page, "v0.2.0") + + if !strings.Contains(got, "[auth](/cli/auth-customization)") { + t.Errorf("./ sibling not resolved: %q", got) + } +} + +func TestRewriteRelative_UnmappedUpstreamFileBecomesGitHubURL(t *testing.T) { + // ../../ROADMAP.md is a real file upstream but is not mirrored, so the + // reader should still reach it on GitHub rather than a 404. + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/reference/api", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/references/api-reference.md"} + + body := "See [the roadmap](../../ROADMAP.md) and [a proposal](../proposals/patterns.md)." + got := r.RewriteRelative(body, page, "v0.22.0") + + if !strings.Contains(got, "[the roadmap](https://github.com/livetemplate/livetemplate/blob/v0.22.0/ROADMAP.md)") { + t.Errorf("repo-root file not rewritten to GitHub URL: %q", got) + } + if !strings.Contains(got, "[a proposal](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/proposals/patterns.md)") { + t.Errorf("unmapped sibling not rewritten to GitHub URL: %q", got) + } +} + +func TestRewriteRelative_DirectoryTargetUsesTreeURL(t *testing.T) { + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/guides/observability", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/guides/OBSERVABILITY.md"} + + body := "See [the package](../internal/observe/)." + got := r.RewriteRelative(body, page, "v0.22.0") + + if !strings.Contains(got, "/tree/v0.22.0/docs/internal/observe") { + t.Errorf("directory target should use /tree/, got: %q", got) + } +} + +func TestRewriteRelative_LeavesFencedCodeBlocksAlone(t *testing.T) { + // A relative path inside an example is part of the example. This is the + // regression the blunt ReplaceAll approach would have caused. + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/guides/scaling", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/guides/SCALING.md"} + + body := "Prose [x](../references/api-reference.md).\n" + + "```md\n" + + "[keep me](../references/api-reference.md)\n" + + "```\n" + + "After [y](../references/api-reference.md).\n" + got := r.RewriteRelative(body, page, "v0.22.0") + + if !strings.Contains(got, "[keep me](../references/api-reference.md)") { + t.Errorf("link inside fenced block was rewritten: %q", got) + } + if strings.Count(got, "(/reference/api)") != 2 { + t.Errorf("expected both prose links rewritten, got: %q", got) + } +} + +func TestRewriteRelative_LeavesEscapingAndAbsoluteTargetsAlone(t *testing.T) { + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/reference/api", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/references/api-reference.md"} + + body := "[up](../../../outside.md) [abs](/reference/session) [ext](https://example.com/x.md)" + got := r.RewriteRelative(body, page, "v0.22.0") + + if got != body { + t.Errorf("non-resolvable / already-absolute targets should be untouched:\nbefore: %q\nafter: %q", body, got) + } +} + +func TestRewriteRelative_CrossRepoPathDoesNotMatch(t *testing.T) { + // docs/guides/auth-customization.md is mirrored, but only for the lvt + // repo. The same path resolved inside livetemplate must NOT pick it up. + r := newLinkRewriter(relCfg()) + page := PageEntry{SiteURL: "/guides/scaling", SourceRepo: "https://github.com/livetemplate/livetemplate", SourcePath: "docs/guides/SCALING.md"} + + body := "[auth](./auth-customization.md)" + got := r.RewriteRelative(body, page, "v0.22.0") + + if strings.Contains(got, "/cli/auth-customization") { + t.Errorf("cross-repo path collision: %q", got) + } + if !strings.Contains(got, "blob/v0.22.0/docs/guides/auth-customization.md") { + t.Errorf("expected GitHub fallback for the other repo's path: %q", got) + } +} diff --git a/content/cli/index.md b/content/cli/index.md index 53345b3..8624b26 100644 --- a/content/cli/index.md +++ b/content/cli/index.md @@ -349,7 +349,7 @@ http.Handle("/dashboard", protectedHandler) **Customizing CSS Framework:** -The generated auth templates use Tailwind CSS by default. To use a different CSS framework (Bulma, Pico, or plain HTML), see the [Auth Customization Guide](./auth-customization.md) for complete examples and instructions. +The generated auth templates use Tailwind CSS by default. To use a different CSS framework (Bulma, Pico, or plain HTML), see the [Auth Customization Guide](/cli/auth-customization) for complete examples and instructions. **E2E Testing:** @@ -828,6 +828,6 @@ go run cmd/mysocial/main.go 5. **Deploy** - Build and deploy your app For more information: -- [API Reference](../references/api-reference.md) -- [Template Support Matrix](../references/template-support-matrix.md) +- [API Reference](https://github.com/livetemplate/lvt/blob/v0.2.0/docs/references/api-reference.md) +- [Template Support Matrix](https://github.com/livetemplate/lvt/blob/v0.2.0/docs/references/template-support-matrix.md) - [LiveTemplate Documentation](https://github.com/livetemplate/livetemplate) diff --git a/content/guides/observability.md b/content/guides/observability.md index 32b4983..a23e9e3 100644 --- a/content/guides/observability.md +++ b/content/guides/observability.md @@ -410,5 +410,5 @@ func RequestIDMiddleware(next http.Handler) http.Handler { ## Related Documentation - [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture overview -- [internal/observe/](../internal/observe/) - Package implementation +- [internal/observe/](https://github.com/livetemplate/livetemplate/tree/v0.22.0/docs/internal/observe) - Package implementation - [Go slog documentation](https://pkg.go.dev/log/slog) - Standard library reference diff --git a/content/guides/progressive-complexity.md b/content/guides/progressive-complexity.md index 748a109..d35736b 100644 --- a/content/guides/progressive-complexity.md +++ b/content/guides/progressive-complexity.md @@ -293,7 +293,7 @@ The `pending` state fires instantly on click (before the server even receives th **Trade-offs:** the pending state is client-only — it does not fan out to peer tabs, does not survive reconnect, and cannot drive server-side logic. The action blocks the event loop for its duration (no other clicks or peer pushes until it returns). For loading that is real application state, use 7.3. -See the [Client Attributes Reference — Reactive Attributes](../references/client-attributes.md#reactive-attributes) for the full `lvt-el:*` pattern. +See the [Client Attributes Reference — Reactive Attributes](/reference/client-attributes#reactive-attributes) for the full `lvt-el:*` pattern. ### 7.3 Server-Owned Loading (Tier 1) @@ -377,7 +377,7 @@ The template is identical — `{{if .Loading}}` works the same way. The key guar - **Connection-scoped** — only the originating connection gets the completion render - **Lifetime-bound** — if the connection closes, the goroutine is cancelled and `apply` is skipped -See the [Async API reference](../references/api-reference.md#async) for the full contract. +See the [Async API reference](/reference/api#async) for the full contract. #### Zero-boilerplate with `{{.lvt.Pending}}` @@ -570,7 +570,7 @@ The server determines the client's transport from the HTTP request: ### What Works at Each Level -For a complete feature-by-transport breakdown, see the [Transport Compatibility table](../references/progressive-complexity-reference.md#transport-compatibility) in the reference doc. +For a complete feature-by-transport breakdown, see the [Transport Compatibility table](/reference/progressive-complexity#transport-compatibility) in the reference doc. ### Disabling Progressive Enhancement @@ -586,7 +586,7 @@ When disabled, POST requests from non-JS browsers return JSON instead of HTML. O ## 13. Tier 2: `lvt-*` Attributes -Use `lvt-*` attributes only when standard HTML cannot express the behavior. For the complete attribute reference, see the [Client Attributes Reference](../references/client-attributes.md). +Use `lvt-*` attributes only when standard HTML cannot express the behavior. For the complete attribute reference, see the [Client Attributes Reference](/reference/client-attributes). ### 13.1 Event Bindings Outside Forms @@ -602,7 +602,7 @@ For interactions outside the form submit lifecycle — hover effects, focus/blur > **Prefer Tier 1 when possible:** For buttons that trigger actions, use `
` + ` ``` -See [Loading States §7.3](../guides/progressive-complexity.md#73-server-owned-loading-tier-1) +See [Loading States §7.3](/guides/progressive-complexity#73-server-owned-loading-tier-1) for the full comparison of loading approaches. --- diff --git a/content/reference/authentication.md b/content/reference/authentication.md index 33545b3..6e37022 100644 --- a/content/reference/authentication.md +++ b/content/reference/authentication.md @@ -457,4 +457,4 @@ LiveTemplate's `Redirect()` method automatically prevents open redirects by: - [Server Actions Reference](server-actions.md) - Push updates from server-side code - [Session Reference](session.md) - Session stores and connection management - [Error Handling](error-handling.md) - Validation and error display -- [Scaling Guide](../guides/SCALING.md) - Redis-backed session stores for distributed deployments +- [Scaling Guide](/guides/scaling) - Redis-backed session stores for distributed deployments diff --git a/content/reference/client-attributes.md b/content/reference/client-attributes.md index 8c1b99a..b51ddc4 100644 --- a/content/reference/client-attributes.md +++ b/content/reference/client-attributes.md @@ -403,7 +403,7 @@ These execute client-side with no server round-trip. ``` -> For choosing between client-owned pending (`lvt-el:*:on:pending`) and server-owned loading (`{{if .Loading}}`), see [Loading States](../guides/progressive-complexity.md#7-loading-states) in the Progressive Complexity Guide. +> For choosing between client-owned pending (`lvt-el:*:on:pending`) and server-owned loading (`{{if .Loading}}`), see [Loading States](/guides/progressive-complexity#7-loading-states) in the Progressive Complexity Guide. **Form Reset on Success:** @@ -800,7 +800,7 @@ Any form inside a `` that completes successfully will have its parent di A `` inside a `` closes the dialog immediately on submit (before the server responds). Use this only when you don't need server-side validation feedback inside the dialog. -See [Progressive Complexity Guide — Dialogs](../guides/progressive-complexity.md#5-dialogs) for the full walkthrough. +See [Progressive Complexity Guide — Dialogs](/guides/progressive-complexity#5-dialogs) for the full walkthrough. ### Server-managed modals @@ -1191,5 +1191,5 @@ form.addEventListener('lvt:pending', (e) => { - **[Go API Reference](https://pkg.go.dev/github.com/livetemplate/livetemplate)** - Server-side API - **[Error Handling Reference](error-handling.md)** - Validation, error display, client-side handling - **[Template Support Matrix](template-support-matrix.md)** - Supported Go template features -- **[Architecture](../design/ARCHITECTURE.md)** - System architecture -- **[Contributing Guide](../../CONTRIBUTING.md)** - How to contribute +- **[Architecture](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/design/ARCHITECTURE.md)** - System architecture +- **[Contributing Guide](/contributing/livetemplate)** - How to contribute diff --git a/content/reference/configuration.md b/content/reference/configuration.md index 3c990f6..dc9cf8f 100644 --- a/content/reference/configuration.md +++ b/content/reference/configuration.md @@ -425,6 +425,6 @@ if err := envConfig.Validate(); err != nil { ## See Also -- [ROADMAP.md](../../ROADMAP.md) - Project roadmap -- [OBSERVABILITY.md](../guides/OBSERVABILITY.md) - Logging and metrics guide -- [SCALING.md](../guides/SCALING.md) - Scaling recommendations +- [ROADMAP.md](https://github.com/livetemplate/livetemplate/blob/v0.22.0/ROADMAP.md) - Project roadmap +- [OBSERVABILITY.md](/guides/observability) - Logging and metrics guide +- [SCALING.md](/guides/scaling) - Scaling recommendations diff --git a/content/reference/controller-pattern.md b/content/reference/controller-pattern.md index 000db4e..b2df6d1 100644 --- a/content/reference/controller-pattern.md +++ b/content/reference/controller-pattern.md @@ -452,7 +452,7 @@ func (c *NotificationController) AddMessage(state NotificationState, ctx *livete } ``` -> `TriggerAction` is also the mechanism behind the server-owned loading pattern (set `Loading=true`, spawn a goroutine, trigger a second action to clear it). See [Loading States §7.3](../guides/progressive-complexity.md#73-server-owned-loading-tier-1) in the Progressive Complexity Guide. +> `TriggerAction` is also the mechanism behind the server-owned loading pattern (set `Loading=true`, spawn a goroutine, trigger a second action to clear it). See [Loading States §7.3](/guides/progressive-complexity#73-server-owned-loading-tier-1) in the Progressive Complexity Guide. ### Cross-Tab Updates with Subscribe + Publish diff --git a/content/reference/limitations.md b/content/reference/limitations.md index c8159b5..8ec7d07 100644 --- a/content/reference/limitations.md +++ b/content/reference/limitations.md @@ -8,7 +8,7 @@ source_commit: "22a4853506a682583b511e470bdd1e6193f4d5fe" # Current Limitations -Known limitations of LiveTemplate, organized by category. Each entry includes the impact, workaround, and current status. For planned improvements, see the [Roadmap](../../ROADMAP.md). +Known limitations of LiveTemplate, organized by category. Each entry includes the impact, workaround, and current status. For planned improvements, see the [Roadmap](https://github.com/livetemplate/livetemplate/blob/v0.22.0/ROADMAP.md). All limitations verified against the current codebase. @@ -27,7 +27,7 @@ These Go template constructs trigger a fallback to HTML segmentation, which prod | `{{block}}` with dynamic template names | Use `{{template "name" .}}` with static names | By design (fallback) | | `iter.Seq` ranges | Collect iterator to slice before passing to template | Blocked on Go templates | -See [HTML Fallback Coverage](../roadmap/html-fallback-coverage.md) for test coverage details and [Template Support Matrix](template-support-matrix.md) for full Go template feature support. +See [HTML Fallback Coverage](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/roadmap/html-fallback-coverage.md) for test coverage details and [Template Support Matrix](template-support-matrix.md) for full Go template feature support. --- @@ -40,7 +40,7 @@ These features require the JavaScript client (fetch or WebSocket transport). Sta | Standalone buttons outside `` | Wrap in `` | Button click events require JS to intercept | | `Change()` live input binding | N/A — form is submit-only | Requires client to detect input changes and send to server | | `form.name` routing | Use `button name` instead | JS client reads `form.name` as an action router — standard HTML POST ignores it as a routing signal | -| `lvt-*` attributes | Use standard HTML equivalents (see [Progressive Complexity Guide](../guides/progressive-complexity.md)) | Custom attributes require JS to interpret | +| `lvt-*` attributes | Use standard HTML equivalents (see [Progressive Complexity Guide](/guides/progressive-complexity)) | Custom attributes require JS to interpret | | Server push / broadcast | N/A — poll or page reload | Requires WebSocket connection | | SPA navigation (link interception) | Standard full-page navigation | Requires JS to intercept clicks and use `fetch()` | @@ -102,14 +102,14 @@ Use `ctx.IsHTTP()` to check which transport is active in an action method. | State cloning JSON round-trip | Per-session cost on first request | Keep state small; subsequent renders are fast (~3 KB, 61 allocs) | | HTML fallback parsing (3.05% of allocations) | Triggered by unsupported template constructs (see Template Features above) | Improve template construct coverage to reduce fallback frequency | -See [Known Bottlenecks](../performance/known-bottlenecks.md) for detailed profiling data and optimization history. +See [Known Bottlenecks](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/performance/known-bottlenecks.md) for detailed profiling data and optimization history. --- ## See Also -- [Roadmap](../../ROADMAP.md) — Planned improvements and feature timeline +- [Roadmap](https://github.com/livetemplate/livetemplate/blob/v0.22.0/ROADMAP.md) — Planned improvements and feature timeline - [Session Reference — State Safety](session.md#state-safety) — Enforcement layers for state purity and session isolation - [Template Support Matrix](template-support-matrix.md) — Supported Go template features -- [HTML Fallback Coverage](../roadmap/html-fallback-coverage.md) — Fallback trigger test coverage -- [Known Bottlenecks](../performance/known-bottlenecks.md) — Performance profiling and optimization +- [HTML Fallback Coverage](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/roadmap/html-fallback-coverage.md) — Fallback trigger test coverage +- [Known Bottlenecks](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/performance/known-bottlenecks.md) — Performance profiling and optimization diff --git a/content/reference/navigate.md b/content/reference/navigate.md index 74a4d23..5f3c1b9 100644 --- a/content/reference/navigate.md +++ b/content/reference/navigate.md @@ -121,7 +121,7 @@ The load-bearing test is `TestNavigateActionReMountsWithNewQueryData` in `naviga 3. Sends `{action: "__navigate__", data: {s: "beta"}}` over the same WS. 4. Confirms the next render flips `Selected` to `"beta"` and bumps `MountCount` to `2` — proving Mount re-ran without any reconnect. -Browser-level chromedp tests live in the lvt repo at `e2e/livetemplate_core_test.go` per the [test strategy](../../CLAUDE.md). Both layers must stay green. +Browser-level chromedp tests live in the lvt repo at `e2e/livetemplate_core_test.go` per the [test strategy](https://github.com/livetemplate/livetemplate/blob/v0.22.0/CLAUDE.md). Both layers must stay green. --- @@ -150,6 +150,6 @@ repo (`data-lvt-heartbeat-ms`). - [Controller+State Pattern](controller-pattern.md) — Mount-time conventions - [Client Attributes](client-attributes.md) — `lvt-nav:no-intercept` opt-out -- [Standard HTML Reactivity](../guides/standard-html-reactivity.md) — Why navigation is a Tier 1 concern +- [Standard HTML Reactivity](/guides/standard-html-reactivity) — Why navigation is a Tier 1 concern - [PR #344](https://github.com/livetemplate/livetemplate/pull/344) — Original implementation - Follow-up issues: [#345](https://github.com/livetemplate/livetemplate/issues/345) (`ClearAllFlash`), [#346](https://github.com/livetemplate/livetemplate/issues/346) (peer-fan-out inside `Mount` on navigate — see `ctx.Publish` to `SelfTopic()`), [#347](https://github.com/livetemplate/livetemplate/issues/347), [#348](https://github.com/livetemplate/livetemplate/issues/348) diff --git a/content/reference/progressive-complexity.md b/content/reference/progressive-complexity.md index 5899169..a88628c 100644 --- a/content/reference/progressive-complexity.md +++ b/content/reference/progressive-complexity.md @@ -8,7 +8,7 @@ source_commit: "22a4853506a682583b511e470bdd1e6193f4d5fe" # Progressive Complexity Reference -Quick-reference for how standard HTML maps to LiveTemplate behavior. For the learning walkthrough, see the [Progressive Complexity Guide](../guides/progressive-complexity.md). For `lvt-*` attributes, see the [Client Attributes Reference](client-attributes.md). +Quick-reference for how standard HTML maps to LiveTemplate behavior. For the learning walkthrough, see the [Progressive Complexity Guide](/guides/progressive-complexity). For `lvt-*` attributes, see the [Client Attributes Reference](client-attributes.md). --- diff --git a/content/reference/pubsub.md b/content/reference/pubsub.md index 028cef0..6aa3806 100644 --- a/content/reference/pubsub.md +++ b/content/reference/pubsub.md @@ -10,7 +10,7 @@ source_commit: "22a4853506a682583b511e470bdd1e6193f4d5fe" Cross-instance messaging for horizontally scaled deployments. -For server-initiated actions, see [Server Actions](server-actions.md). For scaling tiers and Redis configuration, see [Scaling Guide](../guides/SCALING.md). +For server-initiated actions, see [Server Actions](server-actions.md). For scaling tiers and Redis configuration, see [Scaling Guide](/guides/scaling). ## Overview @@ -160,7 +160,7 @@ LiveTemplate uses two independent isolation models: ### Session Isolation (State Boundaries) -Handled by the session store and connection registry. All connections with the same `groupID` share the same state instance. Different groups have completely separate state. This is unaffected by pubsub. See [Multi-Session Isolation](../design/multi-session-isolation.md) for details. +Handled by the session store and connection registry. All connections with the same `groupID` share the same state instance. Different groups have completely separate state. This is unaffected by pubsub. See [Multi-Session Isolation](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/design/multi-session-isolation.md) for details. ### Message Routing Isolation (PubSub) @@ -265,6 +265,6 @@ Grep for `event=topic_action_subscribe_failed` in production logs (the structure - [Server Actions Reference](server-actions.md) — `TriggerAction` API - [Session Reference](session.md) — Session stores and connection management -- [Multi-Session Isolation](../design/multi-session-isolation.md) — State isolation model -- [Scaling Guide](../guides/SCALING.md) — Redis configuration and scaling tiers +- [Multi-Session Isolation](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/design/multi-session-isolation.md) — State isolation model +- [Scaling Guide](/guides/scaling) — Redis configuration and scaling tiers - [Configuration Reference](CONFIGURATION.md) — Environment variables and WebSocket settings diff --git a/content/reference/server-actions.md b/content/reference/server-actions.md index cd72f81..b4cdb12 100644 --- a/content/reference/server-actions.md +++ b/content/reference/server-actions.md @@ -541,7 +541,7 @@ buffer or replay it. The cookie-bound `groupID` is stable across reconnects, so the *next* `TriggerAction` after the WebSocket comes back will reach the user, but the dispatch that fired during the gap is gone. -This is a deliberate design — see the [TriggerAction reconnect-buffering proposal](../proposals/triggeraction-reconnect-buffering.md). +This is a deliberate design — see the [TriggerAction reconnect-buffering proposal](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/proposals/triggeraction-reconnect-buffering.md). ### Detecting the gap @@ -622,7 +622,7 @@ Two rules cover the gap: 1. **Push handlers must be idempotent.** A handler that runs once must produce the same final state as one that runs twice. The - [reconnect-during-loading double-fire race documented under Implementation Notes in `patterns.md`](../proposals/patterns.md#implementation-notes-accumulated-from-completed-sessions) + [reconnect-during-loading double-fire race documented under Implementation Notes in `patterns.md`](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/proposals/patterns.md#implementation-notes-accumulated-from-completed-sessions) makes this concrete: if the client disconnects and reconnects while a goroutine is still sleeping, two goroutines may race to dispatch — both land successfully on the new connection. Idempotent handlers absorb @@ -707,7 +707,7 @@ once-only audit log, paid-API result stream, etc.) the implicit contract is not enough. Open a new issue referencing [#342](https://github.com/livetemplate/livetemplate/issues/342) and describing the exact non-idempotency. The -[buffering proposal](../proposals/triggeraction-reconnect-buffering.md) +[buffering proposal](https://github.com/livetemplate/livetemplate/blob/v0.22.0/docs/proposals/triggeraction-reconnect-buffering.md) captures the design sketch for the durable variant that would solve it, gated on a real use case. @@ -721,4 +721,4 @@ In multi-instance deployments, `TriggerAction()` automatically publishes to Redi - [Session Reference](session.md) - Session stores and connection management - [Authentication Reference](authentication.md) - User identification and custom authenticators - [PubSub Reference](pubsub.md#topic-subscribe--publish-api) - Topic grammar, ACL, and out-of-band `handler.Publish` -- [Scaling Guide](../guides/SCALING.md) - Horizontal scaling with Redis +- [Scaling Guide](/guides/scaling) - Horizontal scaling with Redis diff --git a/content/reference/session.md b/content/reference/session.md index a7e0cc9..e883334 100644 --- a/content/reference/session.md +++ b/content/reference/session.md @@ -569,4 +569,4 @@ See [Current Limitations](current-limitations.md) for the full limitations refer - [Server Actions Reference](server-actions.md) - TriggerAction API for server-initiated updates - [Authentication Reference](authentication.md) - User identification and custom authenticators - [Current Limitations](current-limitations.md) - All known limitations and workarounds -- [Scaling Guide](../guides/SCALING.md) - Horizontal scaling with Redis +- [Scaling Guide](/guides/scaling) - Horizontal scaling with Redis From 5d9596987b855304cd8db5d5cb5a77498fa0d7da Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Sat, 1 Aug 2026 15:55:15 +0000 Subject: [PATCH 2/6] docs(ia): adopt the four stranded pages, drop the duplicate todos mirror MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five pages existed on disk and in no sidebar. That understates it: tinkerdown only serves nav-registered pages, so all five returned 303 to the home page, and four of them were linked from pages that ARE in the nav — the Recipes hub, the Progressive Complexity guide, Standard HTML Reactivity, the Update Flow. Readers following those links were silently bounced to the site root. Adopted four: - recipes/formnovalidate.md -> Recipes. The sharpest case: it has a live mounted app (cmd/site mounts /apps/draft-form/ and the no-js variant), an embed, and inbound links from five pages including the Recipes hub. - guides/ephemeral-components.md -> Concepts, linked from the Progressive Complexity guide. - reference/progressive-complexity.md -> Reference. Not a duplicate of the guide despite the name — 170 lines of lookup tables against the guide's 777 lines of prose, and the guide links to it explicitly. - cli/ai-assistants.md -> Ecosystem. The only one with no reader-facing inbound links, but it is real ecosystem content, not a stray. Removed one. recipes/apps/todos.md was a stale mirror of the pre-consolidation livetemplate/examples todos/README.md — generic Features/Quick Start/Testing content whose Quick Start says `cd todos`, a path that does not exist in this repo. The nav's "Todos" already points at recipes/todos/index.md, the docs-native deep dive covering the same app properly (auth scoping, why components live outside lvt:"persist", where the recipe stops). Keeping both would put two pages titled "Todos" in front of the reader, which is the problem this phase exists to fix. Its one real inbound link, from the Update Flow page, now points at the deep dive. No nav landing pages moved, so e2e/docs_ia_test.go's section->page contract is unchanged; it passes as-is along with breadcrumb and staging. Every markdown file under content/ is now either nav-registered or explicitly sidebar: false. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QzC2djPjPHkNJgPzFpMX7v --- content/recipes/apps/todos.md | 120 --------------------------- content/recipes/architecture-flow.md | 2 +- content/tinkerdown.yaml | 8 ++ 3 files changed, 9 insertions(+), 121 deletions(-) delete mode 100644 content/recipes/apps/todos.md diff --git a/content/recipes/apps/todos.md b/content/recipes/apps/todos.md deleted file mode 100644 index f19507a..0000000 --- a/content/recipes/apps/todos.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: "Todos" -description: "A full LiveTemplate todo app with auth, SQLite persistence, validation, search, sorting, components, and realtime peer refresh." -source_repo: "https://github.com/livetemplate/examples" -source_path: "todos/README.md" -source_commit: "948ce2e3c9de974e139db8b4b8a2fb27054561d8" ---- - -# LiveTemplate Todo App - -A real-time todo application demonstrating LiveTemplate's controller pattern with SQLite persistence, basic authentication, search, sorting, and pagination. Styled with [Pico CSS](https://picocss.com/). - -## Features - -- **Basic authentication** - Per-user todo lists (alice/password, bob/password) -- **Add todos** - Create new tasks via form submission with validation -- **Toggle completion** - Mark tasks as done/undone with checkboxes -- **Delete todos** - Remove individual tasks -- **Clear completed** - Bulk remove all completed tasks -- **Search** - Filter todos by text -- **Sort** - Newest first, oldest first, alphabetical (A-Z / Z-A) -- **Pagination** - 3 items per page with navigation controls -- **Live statistics** - Real-time total, completed, and remaining counts -- **Reactive updates** - Changes broadcast to all connected clients -- **SQLite persistence** - Todos survive server restarts - -## Quick Start - -```bash -cd todos -go run . -``` - -Open and log in with `alice` / `password`. - -With a custom port: - -```bash -PORT=8081 go run . -``` - -## How It Works - -### Controller Pattern - -The app uses LiveTemplate's controller pattern where each action maps to a typed method: - -```go -type TodoController struct { - Queries *db.Queries -} - -func (c *TodoController) Add(state TodoState, ctx *livetemplate.Context) (TodoState, error) { - var input AddInput - if err := ctx.BindAndValidate(&input, validate); err != nil { - return state, err - } - // Create todo in database, reload list - return c.loadTodos(dbCtx, state, ctx.UserID()) -} - -func (c *TodoController) Toggle(state TodoState, ctx *livetemplate.Context) (TodoState, error) { ... } -func (c *TodoController) Delete(state TodoState, ctx *livetemplate.Context) (TodoState, error) { ... } -func (c *TodoController) ClearCompleted(state TodoState, ctx *livetemplate.Context) (TodoState, error) { ... } -func (c *TodoController) Search(state TodoState, ctx *livetemplate.Context) (TodoState, error) { ... } -func (c *TodoController) Sort(state TodoState, ctx *livetemplate.Context) (TodoState, error) { ... } -func (c *TodoController) NextPage(state TodoState, ctx *livetemplate.Context) (TodoState, error) { ... } -func (c *TodoController) PrevPage(state TodoState, ctx *livetemplate.Context) (TodoState, error) { ... } -``` - -Actions are routed from HTML via form `name` and button `name` attributes (Tier 1 pattern): - -```html - - - - - - - -
- - -
- - - -``` - -### Authentication - -Basic auth with hardcoded demo users. `ctx.UserID()` returns the authenticated username, used to isolate each user's todos in SQLite: - -```go -auth := livetemplate.NewBasicAuthenticator(func(username, password string) (bool, error) { - users := map[string]string{"alice": "password", "bob": "password"} - pass, ok := users[username] - return ok && pass == password, nil -}) -``` - -### Database - -SQLite via [sqlc](https://sqlc.dev/)-generated queries. The `db/` directory contains generated code from `queries.sql`. Schema migrations run automatically on startup, including detection and recreation of outdated schemas. - -## Testing - -### Browser E2E Test - -```bash -go test -v -run TestTodosE2E -``` - -Requires Docker for Chrome headless testing. - -## Development Notes - -- **Port**: Defaults to `:8080`, override with `PORT` environment variable -- **Database**: `todos.db` in the current directory (`:memory:` when `TEST_MODE=1`) -- **Client Library**: Served via `e2etest.ServeClientLibrary` in dev mode, CDN in production diff --git a/content/recipes/architecture-flow.md b/content/recipes/architecture-flow.md index 61cbbf6..ec5b778 100644 --- a/content/recipes/architecture-flow.md +++ b/content/recipes/architecture-flow.md @@ -82,7 +82,7 @@ Open browser DevTools → Network → WS to watch the WebSocket frames flow. The ## What you can change to see this in action -Open the [todos example](/recipes/apps/todos) in two browser tabs. Add an item in tab 1 — it appears in tab 2 within ~30ms because each tab opted into peer fan-out via `ctx.Subscribe(ctx.SelfTopic())` in `Mount`, and the controller calls `ctx.Publish(ctx.SelfTopic(), "RefreshTodos", nil)` after each shared mutation. +Open the [todos example](/recipes/todos/) in two browser tabs. Add an item in tab 1 — it appears in tab 2 within ~30ms because each tab opted into peer fan-out via `ctx.Subscribe(ctx.SelfTopic())` in `Mount`, and the controller calls `ctx.Publish(ctx.SelfTopic(), "RefreshTodos", nil)` after each shared mutation. ## How this page works diff --git a/content/tinkerdown.yaml b/content/tinkerdown.yaml index 8ee7472..f98d5ef 100644 --- a/content/tinkerdown.yaml +++ b/content/tinkerdown.yaml @@ -39,6 +39,8 @@ navigation: path: "guides/progressive-complexity.md" - title: "The lvt-* Decision Tree" path: "recipes/progressive-complexity-tree.md" + - title: "Ephemeral Components" + path: "guides/ephemeral-components.md" - title: "Controller & State Pattern" path: "reference/controller-pattern.md" - title: "Error Handling" @@ -61,6 +63,8 @@ navigation: path: "recipes/index.md" - title: "Counter" path: "recipes/apps/counter.md" + - title: "Skip Validation (formnovalidate)" + path: "recipes/formnovalidate.md" - title: "Counter (Deep Dive)" path: "recipes/counter/index.md" - title: "Progressive Enhancement" @@ -212,6 +216,8 @@ navigation: path: "reference/navigate.md" - title: "Template Support" path: "reference/template-support-matrix.md" + - title: "Progressive Complexity (Reference)" + path: "reference/progressive-complexity.md" - title: "Limitations" path: "reference/limitations.md" @@ -242,6 +248,8 @@ navigation: path: "cli/index.md" - title: "CLI: Auth Customization" path: "cli/auth-customization.md" + - title: "CLI: AI Assistants" + path: "cli/ai-assistants.md" - title: "CLI: Components" path: "cli/components.md" - title: "CLI: Testing" From 57cde49dbb7ce59a827f91f8ec989aa1e42607be Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Sat, 1 Aug 2026 16:00:37 +0000 Subject: [PATCH 3/6] docs: make the changelog self-syncing, fix stranded-page blindness in the walk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four fixes that all trace back to pages the site claimed to maintain and did not. changelog.md said "The Phase 3 sync action will keep each section in step with its source on every release". No such entry was ever added to source-of-truth.yaml, so the page never synced and froze at livetemplate v0.8.23 while the library shipped v0.22.0 — 1568 hand-pasted lines, wrong for over a year of releases. cmd/sync maps one source file to one page and cannot concatenate four, so the page is split per repo: /changelog/{livetemplate,client,cli} are now real sync entries, and /changelog is a short index explaining how the server and client versions are pinned to each other. They currently read v0.22.0 / v0.20.0 / v0.2.0 and will stay current without anyone touching them. TestSidebarWalk accepted any 303 with a comment about /cli -> /cli/. But tinkerdown serves only nav-registered pages and bounces everything else to the site root with a 303, and the walk followed it to the home page's 200 — so a stranded page was indistinguishable from a healthy one. That is how /guides/ephemeral-components sat in this very list while being unreachable. The walk now refuses to follow redirects and accepts a 303 only when it points at the same path's trailing-slash variant. Turning that on immediately failed three URLs that had been passing: /recipes/counter, /recipes/todos and /recipes/progressive-enhancement are /index.md pages that tinkerdown serves ONLY with a trailing slash, and the bare form redirects to the site root. Nine content links used the bare form — including the Recipes hub's own "Todos" link and the Learn spine's pointer to Counter deeper — so readers following them landed on the home page. All nine fixed, and the walk's own list corrected. Six recipes/apps pages still declared source_repo: livetemplate/examples with a source_commit from that repo, years after the consolidation made them docs-native. Nothing syncs them, so the frontmatter was pure fiction — and it drives the "Edit this page" link, sending contributors to a repo where their edit would be lost. Corrected to this repo, and the index page's prose claiming the pages are mirrored corrected with it. Also fills the Recipes hub's Apps list, which named four apps out of nine — login, seat-picker, shared-notepad, file-tree and upload-modes were absent despite all being in the sidebar. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01QzC2djPjPHkNJgPzFpMX7v --- content/_meta/source-of-truth.yaml | 16 + content/changelog.md | 1590 +---------------- content/changelog/cli.md | 180 ++ content/changelog/client.md | 871 +++++++++ content/changelog/livetemplate.md | 1281 +++++++++++++ content/getting-started/your-first-app.md | 2 +- content/recipes/apps/chat.md | 5 +- content/recipes/apps/counter.md | 5 +- content/recipes/apps/flash-messages.md | 5 +- content/recipes/apps/index.md | 7 +- .../recipes/apps/progressive-enhancement.md | 5 +- content/recipes/apps/seat-picker.md | 2 +- content/recipes/apps/ws-disabled.md | 5 +- content/recipes/index.md | 11 +- .../recipes/progressive-enhancement/index.md | 4 +- content/recipes/pubsub.md | 2 +- content/recipes/server-push.md | 2 +- content/recipes/todos/index.md | 2 +- content/tinkerdown.yaml | 6 + e2e/staging_test.go | 53 +- 20 files changed, 2453 insertions(+), 1601 deletions(-) create mode 100644 content/changelog/cli.md create mode 100644 content/changelog/client.md create mode 100644 content/changelog/livetemplate.md diff --git a/content/_meta/source-of-truth.yaml b/content/_meta/source-of-truth.yaml index 3ba952a..f72acc4 100644 --- a/content/_meta/source-of-truth.yaml +++ b/content/_meta/source-of-truth.yaml @@ -110,6 +110,22 @@ pages: # source-of-truth is this repo. The runnable apps themselves live at # docs/examples// and are mounted by cmd/site. + # ---- Changelogs ---- + # One page per repo. The old single /changelog page concatenated all of + # them by hand and had no entry here, so it never auto-synced and froze at + # livetemplate v0.8.23 while the library shipped v0.22.0. sync maps one + # source file to one page, so the fix is to split rather than concatenate; + # /changelog is now an index linking to these three. + - site_url: /changelog/livetemplate + source_repo: https://github.com/livetemplate/livetemplate + source_path: CHANGELOG.md + - site_url: /changelog/client + source_repo: https://github.com/livetemplate/client + source_path: CHANGELOG.md + - site_url: /changelog/cli + source_repo: https://github.com/livetemplate/lvt + source_path: CHANGELOG.md + # ---- Contributing ---- - site_url: /contributing/livetemplate source_repo: https://github.com/livetemplate/livetemplate diff --git a/content/changelog.md b/content/changelog.md index 59315f4..3a0d41b 100644 --- a/content/changelog.md +++ b/content/changelog.md @@ -1,1568 +1,34 @@ --- title: "Changelog" -description: "Release history across the LiveTemplate Go framework, TypeScript client, lvt CLI, examples, and docs site." +description: "Release history for the LiveTemplate ecosystem — the Go library, the browser client, and the lvt CLI." +source_repo: https://github.com/livetemplate/docs +source_path: content/changelog.md --- # Changelog -The full release history across the four LiveTemplate ecosystem repos. -Per-repo CHANGELOGs remain canonical in their source repos; this page -mirrors them in one place for convenience. The Phase 3 sync action will -keep each section in step with its source on every release. - -> Each section below is the **full** CHANGELOG of the corresponding repo. -> Use Ctrl-F to find a specific version. - - ---- - -## livetemplate (Go framework) - -_Canonical source: [livetemplate/livetemplate/CHANGELOG.md](https://github.com/livetemplate/livetemplate/blob/main/CHANGELOG.md)_ - -## [v0.8.23] - 2026-05-02 - -### Changes - -- refactor: streaming range Phase 8.5 — remove dead keyGen plumbing (#370) (2ef24d11) -- perf: streaming range Phase 7 — type-direct hash + parallel build (#369) (24950bf9) -- feat: streaming range Phase 6 — recursive transition + LargeTable demo (#368) (900d1da8) -- feat: streaming range Phase 5 — benchmark gate + measured §7 numbers (#366) (73cff639) -- feat: streaming range Phase 4 — cleanup + spec update (#365) (45756b72) -- feat: streaming range Phase 3 — caller integration (cutover) (#364) (6d46c35e) -- feat: streaming range Phase 2 — diff entry point (callable, unwired) (#363) (c07977d9) -- feat: streaming range Phase 1 — foundational types (no-op) (#362) (2a28b70d) -- docs(proposals): Phase 0 audit for streaming range rendering (#361) (f075d7b5) -- docs(proposals): streaming range rendering (#360) (89688276) -- docs(proposals): record lvt-scroll-away top edge ship + Pattern #10 status (a5a5b4bc) -- docs(proposals): tick Session 7 boxes + Implementation Notes (895865f1) -- docs(proposals): tick Session 6 boxes + Session 6 implementation notes (83226ab2) -- docs(proposals): patterns Session 5 complete + 3 implementation notes (d6efdacc) - - - -## [v0.8.22] - 2026-04-25 - -### Changes - -- chore: ignore .claude/scheduled_tasks.lock (Claude Code transient state) (b6cb4f52) -- fix: prune expired flash before render (not after sendUpdate) (#359) (ad5f1071) -- docs(proposals): patterns Session 4 complete + 6 implementation notes (747aedce) - - - - - -## [v0.8.21] - 2026-04-22 - -### Bug Fixes - -- eliminate race in Redis pub/sub init and add subscription retry ([#355](https://github.com/livefir/livetemplate/issues/355)) - -### Documentation - -- scroll effect targeting, lvt-scroll-away, and chat recipe ([#356](https://github.com/livefir/livetemplate/issues/356)) -- **proposals:** update patterns for v0.8.19 + v0.8.33 ([#358](https://github.com/livefir/livetemplate/issues/358)) - - - -## [v0.8.20] - 2026-04-21 - -### Documentation - -- update scroll-sentinel to lvt-scroll-sentinel attribute ([#352](https://github.com/livefir/livetemplate/issues/352)) -- automatic client-side state preservation ([#351](https://github.com/livefir/livetemplate/issues/351)) - - - -## [v0.8.19] - 2026-04-18 - -### Documentation - -- **proposals:** Session 3 complete + server-push pattern lessons ([#338](https://github.com/livefir/livetemplate/issues/338)) - -### Features - -- __navigate__ action + flash persist-until-cleared lifecycle ([#344](https://github.com/livefir/livetemplate/issues/344)) - - - -## [v0.8.18] - 2026-04-14 - -### Bug Fixes - -- wire Session.TriggerAction into lifecycle contexts ([#336](https://github.com/livefir/livetemplate/issues/336)) -- **ci:** update [@livetemplate](https://github.com/livetemplate)/client to latest in cross-repo tests ([#328](https://github.com/livefir/livetemplate/issues/328)) - -### Documentation - -- patterns example proposal ([#333](https://github.com/livefir/livetemplate/issues/333)) -- update dialog routing with polyfill context ([#331](https://github.com/livefir/livetemplate/issues/331)) -- README rewrite proposal ([#268](https://github.com/livefir/livetemplate/issues/268)) ([#332](https://github.com/livefir/livetemplate/issues/332)) -- comprehensive documentation overhaul ([#329](https://github.com/livefir/livetemplate/issues/329)) -- **proposals:** patterns session 2 tracker ([#335](https://github.com/livefir/livetemplate/issues/335)) -- **proposals:** patterns session 1 tracker + implementation notes ([#334](https://github.com/livefir/livetemplate/issues/334)) - - - -## [v0.8.17] - 2026-04-10 - -### Bug Fixes - -- parse individual form fields in multipart submissions ([#326](https://github.com/livefir/livetemplate/issues/326)) - -### Documentation - -- update attribute-reduction proposal with Phase 2 completion status ([#324](https://github.com/livefir/livetemplate/issues/324)) - - - -## [v0.8.16] - 2026-04-04 - -### Documentation - -- mark Phase 2E complete in attribute-reduction proposal - -### Features - -- Tier 1 file uploads — HTTP multipart with progress tracking - - - -## [v0.8.15] - 2026-04-04 - -### Bug Fixes - -- unreserve action field, update tests to use lvt-action ([#321](https://github.com/livefir/livetemplate/issues/321)) - -### Documentation - -- mark Phase 1B as complete in progress tracker ([#322](https://github.com/livefir/livetemplate/issues/322)) -- update client-attributes reference for action-fix changes -- add lvt-form:action, lvt-nav: group, lvt-on:change to proposal -- mark Phase 1A complete in attribute-reduction proposal -- attribute reduction proposal — design + implementation plan ([#288](https://github.com/livefir/livetemplate/issues/288)) - - - -## [v0.8.14] - 2026-04-02 - -### Features - -- add AriaDisabled and FlashTag template helpers ([#318](https://github.com/livefir/livetemplate/issues/318)) - - - -## [v0.8.13] - 2026-04-02 - -### Documentation - -- add ephemeral-components guide ([#316](https://github.com/livefir/livetemplate/issues/316)) - - - -## [v0.8.12] - 2026-04-01 - -### Documentation - -- add attribute reduction proposal ([#288](https://github.com/livefir/livetemplate/issues/288)) ([#292](https://github.com/livefir/livetemplate/issues/292)) - -### Features - -- selective state persistence via lvt:"persist" tag ([#308](https://github.com/livefir/livetemplate/issues/308)) -- simplify error rendering with ErrorTag and AriaInvalid helpers ([#307](https://github.com/livefir/livetemplate/issues/307)) - - - -## [v0.8.11] - 2026-04-01 - -### Features - -- add WithEphemeralState() to opt out of state persistence ([#301](https://github.com/livefir/livetemplate/issues/301)) - - - -## [v0.8.10] - 2026-03-31 - -### Bug Fixes - -- skip HTTP POST persistence on action error + add multi-tab dedup logging ([#296](https://github.com/livefir/livetemplate/issues/296)) -- only create .uploads directory when uploads are configured ([#287](https://github.com/livefir/livetemplate/issues/287)) - -### Documentation - -- add Tier 1 file uploads proposal ([#271](https://github.com/livefir/livetemplate/issues/271)) ([#291](https://github.com/livefir/livetemplate/issues/291)) -- state safety, current limitations, and progressive enhancement ([#284](https://github.com/livefir/livetemplate/issues/284)) - -### Features - -- simplify state management and persistence defaults ([#298](https://github.com/livefir/livetemplate/issues/298)) -- make state persistence opt-in via WithStatePersistence() ([#295](https://github.com/livefir/livetemplate/issues/295)) -- per-connection state persists to session store for page refresh ([#290](https://github.com/livefir/livetemplate/issues/290)) - - - -## [v0.8.9] - 2026-03-30 - -### Bug Fixes - -- flash messages not rendered in WebSocket tree-diff mode ([#283](https://github.com/livefir/livetemplate/issues/283)) -- pull latest from remote before starting release ([#281](https://github.com/livefir/livetemplate/issues/281)) - - - -## [v0.8.8] - 2026-03-29 - -### Bug Fixes - -- AsState panics if state contains dependency types ([#273](https://github.com/livefir/livetemplate/issues/273)) - -### Features - -- per-connection state scoping (LiveView-style socket assigns) ([#275](https://github.com/livefir/livetemplate/issues/275)) - -### Breaking change - - -actions no longer auto-broadcast state or persist to SessionStore. - -Key changes: -- Remove auto-broadcast and SessionStore persist from WebSocket action loop -- Add ctx.BroadcastAction() API for explicit cross-connection dispatch -- Restructure WS message loop to select-based event loop (readPump + DispatchChan) -- Add GroupActionMessage type and Redis PubSub support for cross-instance broadcast -- Handle BroadcastAction from both WebSocket and HTTP POST paths - - - -## [v0.8.7] - 2026-03-27 - -### Features - -- formless standalone buttons — remove hidden form ([#263](https://github.com/livefir/livetemplate/issues/263)) - - - -## [v0.8.6] - 2026-03-26 - -### Bug Fixes - -- use current branch name in release script instead of hardcoded main/master -- preserve struct methods in template data map ([#254](https://github.com/livefir/livetemplate/issues/254)) - -### Features - -- communicate Change() capability to client via initial render metadata ([#253](https://github.com/livefir/livetemplate/issues/253)) - - - -## [v0.8.5] - 2026-03-25 - -### Bug Fixes - -- session benchmarks fail with 'client too slow' ([#209](https://github.com/livefir/livetemplate/issues/209)) -- track dynamic pubsub subscriptions for reconnect and wire into mount ([#213](https://github.com/livefir/livetemplate/issues/213)) -- check X-Forwarded-Proto in WebSocket origin checker ([#190](https://github.com/livefir/livetemplate/issues/190)) - -### Code Refactoring - -- move progressive complexity examples to examples repo ([#248](https://github.com/livefir/livetemplate/issues/248)) -- deduplicate generateItemHash into shared keys package ([#208](https://github.com/livefir/livetemplate/issues/208)) -- rewrite parse package with custom AST evaluator ([#199](https://github.com/livefir/livetemplate/issues/199)) - -### Documentation - -- update perf docs with TreeNode pooling investigation results ([#228](https://github.com/livefir/livetemplate/issues/228)) -- update performance docs and baseline for recent optimizations ([#227](https://github.com/livefir/livetemplate/issues/227)) -- update performance docs and baseline for recent optimizations ([#217](https://github.com/livefir/livetemplate/issues/217)) - -### Features - -- progressive complexity model for form handling ([#233](https://github.com/livefir/livetemplate/issues/233)) -- enhance ValidationToMultiError with friendly names and new tags ([#218](https://github.com/livefir/livetemplate/issues/218)) -- add WithTrustForwardedHeaders config option ([#211](https://github.com/livefir/livetemplate/issues/211)) - -### Performance Improvements - -- system card benchmark and per-session memory optimization ([#235](https://github.com/livefir/livetemplate/issues/235)) -- replace encoding/json with json-iterator in hot paths ([#229](https://github.com/livefir/livetemplate/issues/229)) -- reduce allocations with shared statics, buffer pool, and reflection dedup ([#224](https://github.com/livefir/livetemplate/issues/224)) -- replace TreeNode Dynamics map with slice for ~20% speedup ([#220](https://github.com/livefir/livetemplate/issues/220)) -- reduce template parsing allocations by 50-57% per render ([#219](https://github.com/livefir/livetemplate/issues/219)) -- optimize range diffing with pre-computed context ([#212](https://github.com/livefir/livetemplate/issues/212)) -- switch fingerprint hash to FNV-1a; add stress tests ([#205](https://github.com/livefir/livetemplate/issues/205)) - - - -## [v0.8.4] - 2026-03-14 - -### Bug Fixes - -- unify divergent expression evaluation paths ([#176](https://github.com/livefir/livetemplate/issues/176)) ([#179](https://github.com/livefir/livetemplate/issues/179)) -- use cookie-based flash messages instead of URL query params ([#136](https://github.com/livefir/livetemplate/issues/136)) - -### Documentation - -- refresh benchmark baseline and remove stale references ([#185](https://github.com/livefir/livetemplate/issues/185)) -- batch address 9 documentation follow-up issues ([#178](https://github.com/livefir/livetemplate/issues/178)) -- update performance docs to reflect current codebase ([#175](https://github.com/livefir/livetemplate/issues/175)) -- audit and reorganize proposals directory ([#173](https://github.com/livefir/livetemplate/issues/173)) -- replace api-reference.md with Go library API reference ([#164](https://github.com/livefir/livetemplate/issues/164)) -- rewrite uploads.md for Controller+State pattern ([#163](https://github.com/livefir/livetemplate/issues/163)) -- fix broken links in CONFIGURATION.md and client-attributes.md ([#162](https://github.com/livefir/livetemplate/issues/162)) -- fix session.md interface signatures and add missing features ([#161](https://github.com/livefir/livetemplate/issues/161)) -- expand server-actions.md with pubsub package details ([#160](https://github.com/livefir/livetemplate/issues/160)) -- fix controller-pattern.md phantom methods, add missing APIs ([#159](https://github.com/livefir/livetemplate/issues/159)) -- fix authentication.md phantom methods and broken link ([#158](https://github.com/livefir/livetemplate/issues/158)) -- update template-support-matrix.md with current codebase state ([#157](https://github.com/livefir/livetemplate/issues/157)) -- fix spec inaccuracies found during implementation verification ([#156](https://github.com/livefir/livetemplate/issues/156)) -- move lvt-specific guides to lvt repo ([#153](https://github.com/livefir/livetemplate/issues/153)) -- improve new contributor walkthrough guide ([#152](https://github.com/livefir/livetemplate/issues/152)) -- audit specs, design, performance, and CLAUDE.md (Batch 5) ([#149](https://github.com/livefir/livetemplate/issues/149)) -- update configuration and reference docs (Batch 4) ([#147](https://github.com/livefir/livetemplate/issues/147)) -- audit and fix guide documentation (Batch 3) ([#146](https://github.com/livefir/livetemplate/issues/146)) -- regenerate core architecture docs (Batch 2) ([#145](https://github.com/livefir/livetemplate/issues/145)) -- update component import paths in doc comments -- archive 22 completed planning artifacts (Batch 1) ([#144](https://github.com/livefir/livetemplate/issues/144)) -- Add comprehensive documentation overhaul plan and update README index ([#138](https://github.com/livefir/livetemplate/issues/138)) -- fix metric names to match prometheus.go output -- fix internal/observe imports and document TraceMiddleware removal ([#137](https://github.com/livefir/livetemplate/issues/137)) - -### Features - -- integrate LVT_WS_BUFFER_SIZE into EnvConfig system ([#151](https://github.com/livefir/livetemplate/issues/151)) -- support template variable declarations ($c := .) in parser ([#150](https://github.com/livefir/livetemplate/issues/150)) -- support template variable declarations ($c := .) in parser - - - -## [v0.8.3] - 2026-02-27 - -### Bug Fixes - -- skip npm tests in pre-commit when client/ directory is absent -- cache HTTP templates per session to enable diff optimization ([#134](https://github.com/livefir/livetemplate/issues/134)) -- add component attribute to all remaining slog calls ([#132](https://github.com/livefir/livetemplate/issues/132)) -- slog cleanup — error handling, formatting, and component attributes ([#130](https://github.com/livefir/livetemplate/issues/130)) -- enable burst mutation fuzz tests and fix KeyStability invariant ([#118](https://github.com/livefir/livetemplate/issues/118)) -- handle complex insertion patterns in range differential operations ([#113](https://github.com/livefir/livetemplate/issues/113)) - -### Code Refactoring - -- migrate log.Printf to structured slog logging ([#100](https://github.com/livefir/livetemplate/issues/100)) ([#123](https://github.com/livefir/livetemplate/issues/123)) - -### Documentation - -- document auto-key behavioral change in release notes ([#121](https://github.com/livefir/livetemplate/issues/121)) -- document fingerprint-based diff architecture ([#120](https://github.com/livefir/livetemplate/issues/120)) - - - -## [v0.8.2] - 2026-02-02 - -### Features - -- comprehensive fuzz testing framework with TypeScript oracle ([#110](https://github.com/livefir/livetemplate/issues/110)) - - - -## [v0.8.1] - 2026-01-26 - -### Bug Fixes - -- skip Redis tests gracefully when Docker is unavailable ([#109](https://github.com/livefir/livetemplate/issues/109)) -- address Copilot review comments on API accuracy -- correct API references and range operation format in walkthrough - -### Features - -- auto-generated keys for range items without explicit key attribute ([#108](https://github.com/livefir/livetemplate/issues/108)) -- progressive enhancement support for non-JS form submissions ([#102](https://github.com/livefir/livetemplate/issues/102)) - - - -## [v0.8.0] - 2026-01-18 - - - -## [v0.7.12] - 2026-01-10 - -### Bug Fixes - -- preserve statics for conditional blocks in tree updates ([#84](https://github.com/livefir/livetemplate/issues/84)) - - - -## [v0.7.11] - 2026-01-06 - -### Bug Fixes - -- recognize append/prepend patterns to prevent statics resend on load_more ([#83](https://github.com/livefir/livetemplate/issues/83)) - - - -## [v0.7.10] - 2026-01-04 - -### Bug Fixes - -- handle range→else transitions in top-level range handling - - - -## [v0.7.9] - 2026-01-03 - -### Bug Fixes - -- invalidate registry when conditional becomes empty ([#81](https://github.com/livefir/livetemplate/issues/81)) - - - -## [v0.7.8] - 2025-12-27 - -### Bug Fixes - -- **diff:** detect tree node changes when statics differ -- **mount:** enable flash messages on HTTP redirects with query params - - - -## [v0.7.7] - 2025-12-26 - -### Features - -- add per-connection flash messages ([#79](https://github.com/livefir/livetemplate/issues/79)) - - - -## [v0.7.6] - 2025-12-25 - -### Features - -- add query parameter support for Mount and action handlers ([#78](https://github.com/livefir/livetemplate/issues/78)) - - - -## [v0.7.5] - 2025-12-24 - -### Bug Fixes - -- handle non-TreeNode to TreeNode transitions in range updates ([#77](https://github.com/livefir/livetemplate/issues/77)) -- handle non-TreeNode to TreeNode transitions in range updates - - - -## [v0.7.4] - 2025-12-23 - -### Bug Fixes - -- ensure Range.Statics populated for empty→items transitions ([#76](https://github.com/livefir/livetemplate/issues/76)) - - - -## [v0.7.3] - 2025-12-22 - -### Bug Fixes - -- support heterogeneous range items with per-item statics ([#75](https://github.com/livefir/livetemplate/issues/75)) - - - -## [v0.7.2] - 2025-12-20 - -### Bug Fixes - -- add type guard in SetDynamic to prevent raw structs in tree dynamics ([#74](https://github.com/livefir/livetemplate/issues/74)) - -### Features - -- action.go updates for livepage ([#73](https://github.com/livefir/livetemplate/issues/73)) - - - -## [v0.7.1] - 2025-12-14 - -### Bug Fixes - -- mark range statics path in registry for proper caching ([#72](https://github.com/livefir/livetemplate/issues/72)) - - - -## [v0.7.0] - 2025-12-10 - -### Documentation - -- update all documentation for Controller+State API (v0.7.0) ([#70](https://github.com/livefir/livetemplate/issues/70)) - -### Features - -- add component template registration support ([#71](https://github.com/livefir/livetemplate/issues/71)) - - - -## [v0.6.0] - 2025-12-04 - - - -## [v0.5.2] - 2025-12-03 - -### Documentation - -- update client-attributes reference with reactive attributes and more ([#65](https://github.com/livefir/livetemplate/issues/65)) -- add reactive attributes proposal ([#64](https://github.com/livefir/livetemplate/issues/64)) - -### Features - -- store pattern redesign with automatic method dispatch ([#66](https://github.com/livefir/livetemplate/issues/66)) - - - -## [v0.5.1] - 2025-11-30 - -### Documentation - -- add authentication and session reference documentation ([#63](https://github.com/livefir/livetemplate/issues/63)) - - - -## [v0.5.0] - 2025-11-30 - -### Documentation - -- update documentation for Session API ([#62](https://github.com/livefir/livetemplate/issues/62)) -- improve README structure and narrative flow ([#59](https://github.com/livefir/livetemplate/issues/59)) - -### Features - -- add Session API for server-initiated actions ([#61](https://github.com/livefir/livetemplate/issues/61)) -- add HTTP methods to ActionContext for authentication (v0.5) ([#60](https://github.com/livefir/livetemplate/issues/60)) -- add coverage targets to Makefile ([#57](https://github.com/livefir/livetemplate/issues/57)) - - - -## [v0.4.2-debug.2] - 2025-11-22 - -### Bug Fixes - -- add log package import for debug logging - -### Documentation - -- update investigation with breakthrough findings from timing instrumentation - - - -## [v0.4.2-debug.1] - 2025-11-22 - - - -## [v0.4.1] - 2025-11-22 - -### Bug Fixes - -- use async WebSocket Send() instead of blocking WriteMessage() ([#56](https://github.com/livefir/livetemplate/issues/56)) - - - -## [v0.4.0] - 2025-11-22 - -### Code Refactoring - -- **registry:** achieve Grade A code quality for async WebSocket ([#55](https://github.com/livefir/livetemplate/issues/55)) - - - -## [v0.3.2] - 2025-11-20 - -### Bug Fixes - -- convert validation error field names to lowercase - - - -## [v0.3.1] - 2025-11-19 - -### Bug Fixes - -- send live tree update after upload completion ([#54](https://github.com/livefir/livetemplate/issues/54)) -- send live tree update after upload completion ([#53](https://github.com/livefir/livetemplate/issues/53)) - -### Features - -- Phoenix LiveView-inspired file upload system v0.3.0 ([#52](https://github.com/livefir/livetemplate/issues/52)) - - - -## [v0.3.0] - 2025-11-12 - -### Bug Fixes - -- use GOWORK=off in release script to avoid workspace issues -- address minor code review issues -- address code review feedback - -### Code Refactoring - -- make New() fail-fast on template parsing errors ([#51](https://github.com/livefir/livetemplate/issues/51)) - -### Documentation - -- add optimization task list to performance bottlenecks -- add performance section to README -- add performance characteristics analysis -- add comprehensive benchmarking guide -- document performance bottlenecks from profiling -- add design and implementation plan - -### Performance Improvements - -- address code review recommendations -- establish performance baseline -- add end-to-end user journey benchmarks -- add end-to-end template benchmarks -- add Phase 4 (Render) and Phase 5 (Send) benchmarks -- add Phase 3 (Diff) benchmarks -- add Phase 2 (Build) benchmarks -- add Phase 1 (Parse) benchmarks - - - -## [v0.2.1] - 2025-11-11 - -### Bug Fixes - -- allow template discovery in internal directories for multi kit support -- template auto-discovery for go run and lvt serve ([#49](https://github.com/livefir/livetemplate/issues/49)) -- improve template auto-discovery robustness ([#47](https://github.com/livefir/livetemplate/issues/47)) - -### Documentation - -- remove version-specific references from contributor walkthrough -- create comprehensive contributor walkthrough for 5-phase architecture -- simplify README to focus on core value proposition ([#48](https://github.com/livefir/livetemplate/issues/48)) - - - -## [v0.2.0] - 2025-11-09 - -### Code Refactoring - -- improve key generation and fingerprinting robustness -- complete Phase 2 - move 4 functions to internal packages ([#44](https://github.com/livefir/livetemplate/issues/44)) -- align template.go with 5-phase architecture ([#43](https://github.com/livefir/livetemplate/issues/43)) -- reduce public API surface area from 11 to 7 files ([#46](https://github.com/livefir/livetemplate/issues/46)) -- **conditional:** eliminate duplication and improve error handling ([#40](https://github.com/livefir/livetemplate/issues/40)) -- **context:** achieve Grade A code quality ([#31](https://github.com/livefir/livetemplate/issues/31)) -- **field:** achieve Grade A code quality ([#36](https://github.com/livefir/livetemplate/issues/36)) -- **fingerprint:** fix circular detection and improve robustness -- **helpers:** achieve Grade A code quality ([#35](https://github.com/livefir/livetemplate/issues/35)) -- **parse:** achieve Grade A code quality ([#38](https://github.com/livefir/livetemplate/issues/38)) -- **parse:** achieve Grade A code quality ([#41](https://github.com/livefir/livetemplate/issues/41)) -- **prepare:** achieve Grade A code quality ([#34](https://github.com/livefir/livetemplate/issues/34)) -- **range:** achieve Grade A code quality ([#37](https://github.com/livefir/livetemplate/issues/37)) -- **range_ops:** achieve Grade A code quality ([#33](https://github.com/livefir/livetemplate/issues/33)) -- **render:** achieve Grade A code quality ([#42](https://github.com/livefir/livetemplate/issues/42)) -- **render:** performance, security, and quality improvements ([#27](https://github.com/livefir/livetemplate/issues/27)) -- **template:** achieve Grade A- code quality with 5-phase architecture ([#45](https://github.com/livefir/livetemplate/issues/45)) -- **tree_compare:** achieve Grade A code quality ([#32](https://github.com/livefir/livetemplate/issues/32)) -- **types:** achieve Grade A quality with comprehensive tests and documentation -- **var_context:** achieve Grade A code quality ([#39](https://github.com/livefir/livetemplate/issues/39)) -- **wrapper:** improve security, correctness, and robustness - Grade A ([#29](https://github.com/livefir/livetemplate/issues/29)) - - - -## [v0.1.3] - 2025-11-07 - - - -## [ls] - 2025-11-07 - -### Bug Fixes - -- update release script for Go-only releases -- use absolute paths for replace directives in cross-repo tests -- resolve race conditions in RedisBroadcaster - -### Code Refactoring - -- API reduction for v0.2.0 - reduce public API surface area ([#23](https://github.com/livefir/livetemplate/issues/23)) - -### Documentation - -- update RELEASE.md for Go-only releases - -### Features - -- Code review backlog implementation - Issues [#12](https://github.com/livefir/livetemplate/issues/12)-52 ([#24](https://github.com/livefir/livetemplate/issues/24)) -- add comprehensive unit tests for internal packages ([#22](https://github.com/livefir/livetemplate/issues/22)) - -### BREAKING CHANGE - - -SessionStore methods now require context.Context parameter - -This change adds proper context propagation throughout the session store -layer, enabling timeout control, cancellation, and tracing for all Redis -and session operations. - -Changes to SessionStore interface: -- Get(ctx context.Context, groupID string) Stores -- Set(ctx context.Context, groupID string, stores Stores) -- Delete(ctx context.Context, groupID string) -- List(ctx context.Context) []string - -Implementation updates: - -MemorySessionStore: -- Accepts context parameter for interface compliance -- Operations are in-memory so context not used internally - -RedisSessionStore: -- Uses provided context for all Redis operations -- getWithRetry and execPipelineWithRetry now respect context -- Context-aware sleep during retry backoff -- Checks for context cancellation before each retry attempt - -Benefits: -- Redis operations can be cancelled mid-flight -- Timeouts are properly respected across retry logic -- Trace IDs and request metadata can be propagated -- Better observability in distributed systems -- Prevents resource leaks from hung operations - -Migration guide: -- All SessionStore method calls must now pass context -- Use r.Context() in HTTP handlers for request-scoped context -- Use context.Background() for background operations -- Consider using context.WithTimeout() for bounded operations - -### Breaking Change - - -No - added field to struct, backward compatible. - -Note: Only one pre-existing test failure (TestTemplateGenerateTreeWithFuncMap) - -🤖 Generated with [Claude Code](https://claude.com/claude-code) - - - -## [v0.1.2] - 2025-11-03 - -### Bug Fixes - -- exclude extracted components from test workflow - -### Features - -- add cross-repository testing and local development workflows - - - -## [v0.1.1] - 2025-11-03 - - - -## v0.1.0 - 2025-11-03 - -### Bug Fixes - -- improve binary build and archive naming in release script -- increase test timeout in release script from 30s to 120s -- remove t.Parallel() from e2e tests to prevent timeout deadlocks -- resolve flaky TestConnectionLimits_ConcurrentAccess test -- add LVT_DEV_MODE to todos e2e test and update hardcoded client paths -- set LVT_DEV_MODE=true in test server startup -- correct observability API usage in example -- prevent accidental .golangci.yml restoration -- resolve all golangci-lint issues and enhance CI validation -- **lvt:** prevent auth tests from generating files in commands/internal ([#19](https://github.com/livefir/livetemplate/issues/19)) -- **lvt:** move auth command under lvt gen subcommands ([#17](https://github.com/livefir/livetemplate/issues/17)) - -### Code Refactoring - -- Phase 4 - Extract large functions into internal/diff package -- move remaining build functions to internal/build (Phase 3.2) -- move fingerprinting functions to internal/build -- integrate internal/parse package and remove tree_ast.go -- move tree types to internal/build package -- convert TDD tests to maintainable table-driven format - -### Documentation - -- Update documentation for repository restructuring -- Complete Milestone 2 - Horizontal Scaling Documentation & Implementation ([#20](https://github.com/livefir/livetemplate/issues/20)) -- add first principles document and fix pre-commit hook ([#18](https://github.com/livefir/livetemplate/issues/18)) -- update all docs to reflect v1.0 internal package architecture -- Phase 5 - Migration guide, observability example, and test fixtures -- mark refactoring as complete and ready to merge -- update REFACTORING_PROGRESS.md for Phase 3 completion -- update REFACTORING_PROGRESS.md for Phase 3.1 completion -- update REFACTORING_PROGRESS.md - Phase 2 complete -- add comprehensive observability guide -- comprehensive documentation audit and API accuracy fixes ([#4](https://github.com/livefir/livetemplate/issues/4)) - -### Features - -- update release script to use GitHub CLI and publish npm package -- add testcontainers for Redis testing -- Add deployment stack generation (lvt gen stack) ([#21](https://github.com/livefir/livetemplate/issues/21)) -- create internal/parse package for template parsing -- observability and architecture documentation -- add comprehensive TDD tests for all Go template actions -- implement comprehensive granular fragment support for all template actions -- implement granular range fragment system with CRUD operations -- **lvt:** add lvt gen auth command - Complete (Phases 1-6) ([#15](https://github.com/livefir/livetemplate/issues/15)) - - -[Unreleased]: https://github.com/livefir/livetemplate/compare/v0.8.21...HEAD -[v0.8.21]: https://github.com/livefir/livetemplate/compare/v0.8.20...v0.8.21 -[v0.8.20]: https://github.com/livefir/livetemplate/compare/v0.8.19...v0.8.20 -[v0.8.19]: https://github.com/livefir/livetemplate/compare/v0.8.18...v0.8.19 -[v0.8.18]: https://github.com/livefir/livetemplate/compare/v0.8.17...v0.8.18 -[v0.8.17]: https://github.com/livefir/livetemplate/compare/v0.8.16...v0.8.17 -[v0.8.16]: https://github.com/livefir/livetemplate/compare/v0.8.15...v0.8.16 -[v0.8.15]: https://github.com/livefir/livetemplate/compare/v0.8.14...v0.8.15 -[v0.8.14]: https://github.com/livefir/livetemplate/compare/v0.8.13...v0.8.14 -[v0.8.13]: https://github.com/livefir/livetemplate/compare/v0.8.12...v0.8.13 -[v0.8.12]: https://github.com/livefir/livetemplate/compare/v0.8.11...v0.8.12 -[v0.8.11]: https://github.com/livefir/livetemplate/compare/v0.8.10...v0.8.11 -[v0.8.10]: https://github.com/livefir/livetemplate/compare/v0.8.9...v0.8.10 -[v0.8.9]: https://github.com/livefir/livetemplate/compare/v0.8.8...v0.8.9 -[v0.8.8]: https://github.com/livefir/livetemplate/compare/v0.8.7...v0.8.8 -[v0.8.7]: https://github.com/livefir/livetemplate/compare/v0.8.6...v0.8.7 -[v0.8.6]: https://github.com/livefir/livetemplate/compare/v0.8.5...v0.8.6 -[v0.8.5]: https://github.com/livefir/livetemplate/compare/v0.8.4...v0.8.5 -[v0.8.4]: https://github.com/livefir/livetemplate/compare/v0.8.3...v0.8.4 -[v0.8.3]: https://github.com/livefir/livetemplate/compare/v0.8.2...v0.8.3 -[v0.8.2]: https://github.com/livefir/livetemplate/compare/v0.8.1...v0.8.2 -[v0.8.1]: https://github.com/livefir/livetemplate/compare/v0.8.0...v0.8.1 -[v0.8.0]: https://github.com/livefir/livetemplate/compare/v0.7.12...v0.8.0 -[v0.7.12]: https://github.com/livefir/livetemplate/compare/v0.7.11...v0.7.12 -[v0.7.11]: https://github.com/livefir/livetemplate/compare/v0.7.10...v0.7.11 -[v0.7.10]: https://github.com/livefir/livetemplate/compare/v0.7.9...v0.7.10 -[v0.7.9]: https://github.com/livefir/livetemplate/compare/v0.7.8...v0.7.9 -[v0.7.8]: https://github.com/livefir/livetemplate/compare/v0.7.7...v0.7.8 -[v0.7.7]: https://github.com/livefir/livetemplate/compare/v0.7.6...v0.7.7 -[v0.7.6]: https://github.com/livefir/livetemplate/compare/v0.7.5...v0.7.6 -[v0.7.5]: https://github.com/livefir/livetemplate/compare/v0.7.4...v0.7.5 -[v0.7.4]: https://github.com/livefir/livetemplate/compare/v0.7.3...v0.7.4 -[v0.7.3]: https://github.com/livefir/livetemplate/compare/v0.7.2...v0.7.3 -[v0.7.2]: https://github.com/livefir/livetemplate/compare/v0.7.1...v0.7.2 -[v0.7.1]: https://github.com/livefir/livetemplate/compare/v0.7.0...v0.7.1 -[v0.7.0]: https://github.com/livefir/livetemplate/compare/v0.6.0...v0.7.0 -[v0.6.0]: https://github.com/livefir/livetemplate/compare/v0.5.2...v0.6.0 -[v0.5.2]: https://github.com/livefir/livetemplate/compare/v0.5.1...v0.5.2 -[v0.5.1]: https://github.com/livefir/livetemplate/compare/v0.5.0...v0.5.1 -[v0.5.0]: https://github.com/livefir/livetemplate/compare/v0.4.2-debug.2...v0.5.0 -[v0.4.2-debug.2]: https://github.com/livefir/livetemplate/compare/v0.4.2-debug.1...v0.4.2-debug.2 -[v0.4.2-debug.1]: https://github.com/livefir/livetemplate/compare/v0.4.1...v0.4.2-debug.1 -[v0.4.1]: https://github.com/livefir/livetemplate/compare/v0.4.0...v0.4.1 -[v0.4.0]: https://github.com/livefir/livetemplate/compare/v0.3.2...v0.4.0 -[v0.3.2]: https://github.com/livefir/livetemplate/compare/v0.3.1...v0.3.2 -[v0.3.1]: https://github.com/livefir/livetemplate/compare/v0.3.0...v0.3.1 -[v0.3.0]: https://github.com/livefir/livetemplate/compare/v0.2.1...v0.3.0 -[v0.2.1]: https://github.com/livefir/livetemplate/compare/v0.2.0...v0.2.1 -[v0.2.0]: https://github.com/livefir/livetemplate/compare/v0.1.3...v0.2.0 -[v0.1.3]: https://github.com/livefir/livetemplate/compare/ls...v0.1.3 -[ls]: https://github.com/livefir/livetemplate/compare/v0.1.2...ls -[v0.1.2]: https://github.com/livefir/livetemplate/compare/v0.1.1...v0.1.2 -[v0.1.1]: https://github.com/livefir/livetemplate/compare/v0.1.0...v0.1.1 - ---- - -## @livetemplate/client (TypeScript client) - -_Canonical source: [livetemplate/client/CHANGELOG.md](https://github.com/livetemplate/client/blob/main/CHANGELOG.md)_ - -## [v0.8.40] - 2026-05-02 - -### Changes - -- fix: always run fire-on-change directive scans (#107) (#114) (dff1765) - - - -## [v0.8.39] - 2026-05-02 - -### Changes - -- feat: per-op targeted DOM mutation for range diff ops (#107) (#108) (8f34384) - - - -## [v0.8.38] - 2026-04-28 - -### Changes - -- feat: HTML5 drag-and-drop event support (#101) (#106) (54ebdec) - - - -## [v0.8.37] - 2026-04-28 - -### Changes - -- fix(directives): remove empty style attr after highlight cleanup (#105) (187de33) - - - -## [v0.8.36] - 2026-04-28 - -### Changes - -- feat: lvt-scroll-away top edge for scroll-to-top buttons (#103) (661b8c2) - - - -## [v0.8.35] - 2026-04-27 - -### Changes - -- feat: reconnect WebSocket on visibility change (iOS background fix) (#99) (ef57b41) - - - -## [v0.8.34] - 2026-04-22 - -### Changes - -- fix(release): use explicit refspec to update tracking ref before sync check (#98) (51b5510) -- feat: data-lvt-target for scroll effects + lvt-scroll-away visibility toggle (#94) (860861b) - - - -## [v0.8.33] - 2026-04-20 - -### Changes - -- fix(morphdom): allow child updates inside open dialogs (#93) (ae78517) -- refactor(observer): replace scroll-sentinel id with lvt-scroll-sentinel attribute (#92) (e8666db) - - - -## [v0.8.32] - 2026-04-20 - -### Changes - -- fix(ws): detach handlers before closing socket on disconnect (#91) (f38891d) - - - -## [v0.8.31] - 2026-04-20 - -### Changes - -- fix(morphdom): preserve datalist while connected input is focused (#85) (ef9edea) - - - -## [v0.8.30] - 2026-04-19 - -### Changes - -- feat: hash-driven element activation for deep-linking (#86) (c85d36c) - - - -## [v0.8.29] - 2026-04-18 - -### Changes - -- fix(release): prompt before releasing with un-pushed local commits (54e5b08) -- fix(release): auto-switch to main before releasing (81d3c75) -- fix(morphdom): preserve checkbox/radio checked state across updates (#81) (ab879f7) -- Revert "fix(morphdom): preserve checkbox/radio checked state across updates" (adc9e55) -- fix(morphdom): preserve checkbox/radio checked state across updates (0d791e0) - - - -## [v0.8.28] - 2026-04-18 - -### Changes - -- fix(checkbox): send array of values for multiple same-name checkboxes (#78) (2bca20e) -- chore(release): v0.8.27 (72a925f) -- fix(link-interceptor): fix popstate back/forward navigation regression (053a6b7) - - - -## [v0.8.27] - 2026-04-17 - -### Changes - -- fix(link-interceptor): fix popstate back/forward navigation regression (053a6b7) - - - -## [v0.8.26] - 2026-04-17 - -### Changes - -- feat: lvt-ignore attributes, __navigate__ SPA nav, DOMParser script fix (#72) (966d65d) - - - -## [Unreleased] - -### Added - -- `lvt-ignore` attribute: morphdom escape hatch that skips an element and its entire subtree during diff (equivalent to Phoenix LiveView's `phx-update="ignore"`). Checked on `fromEl` (live DOM) so both server templates and client JS can use it. Use `data-lvt-force-update` on the server's version to bypass and resume diffing. -- `lvt-ignore-attrs` attribute: morphdom escape hatch that preserves user-managed attributes (e.g. `open` on `
`) while still diffing children. Checked on `fromEl` for consistency with `lvt-ignore`. Use `data-lvt-force-update` to bypass. -- In-band `__navigate__` SPA navigation: same-pathname link clicks send `{action:"__navigate__", data:}` over the existing WebSocket instead of fetching new HTML. Requires server-side support (livetemplate/livetemplate#344). -- DOMParser fallback in `updateDOM`: HTML containing `