Skip to content

docs(chat): document the API the app actually has - #138

Merged
adnaan merged 1 commit into
mainfrom
chat-api
Aug 15, 2026
Merged

docs(chat): document the API the app actually has#138
adnaan merged 1 commit into
mainfrom
chat-api

Conversation

@adnaan

@adnaan adnaan commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

recipes/apps/chat.md's tutorial described a framework that does not exist. I flagged this in #137 rather than folding it into a prose PR; this is that fix.

What it claimed

func (s *ChatState) Change(ctx *livetemplate.ActionContext) error {
    switch ctx.Action {
    case "send":
        ctx.Bind(&data)

There is no ActionContext, no Change-with-a-switch dispatch, and no ctx.Bind. The page also:

  • defined func (s *ChatState) Init() error, which nothing calls
  • put sync.RWMutex and the users map on the state struct — both live on the controller, and a mutex on state is copied per connection and guards nothing
  • passed an undefined controller variable to Handle

Someone following the page could not have produced a running program.

What the app actually does

examples/chat/main.go uses the controller pattern — a ChatController singleton holding shared data behind a mutex, a per-connection ChatState, and named action methods dispatched by name (Mount, OnConnect, Join, Send, NewMessage, Leave, plus the two peer handlers).

Steps 2–4 are now built from that file with include= rather than retyped, so they cannot drift again. Same for the "Things to add" snippets:

  • persistence now puts the store on the controller, and notes why state is the wrong place (it clones per connection and round-trips through JSON)
  • rate limiting keys on ctx.GroupID() instead of a per-connection field that opening a second tab would reset
  • chat rooms became a topic, with a note that developer topics are deny-all until named

Two smaller corrections

Reading it closely turned up that "How it works" step 3 named a SendMessage method (it is Send), and the summary claimed four methods where there are eight. Both were mine, from #137.

CONTRIBUTING

Adds the rule that would have caught this — include code, don't retype it — plus a pointer to VOICE.md. That guidance previously lived in livetemplate/examples/CONTRIBUTING.md; that repo is archived and read-only, so this file is now the only place it can live. Worth knowing separately, since content/contributing/examples.md still mirrors from it.

Verification

  • scripts/voice-check.sh green — it caught a load-bearing and four passives in my own new prose before I noticed them
  • tinkerdown validate content/ 98/98, which resolves every include=
  • every include= line range checked to land on a clean function boundary
  • go test ./e2e/... green uncached against the prod image
  • make sweep 194/196 — the 2 are the pre-existing 4.9 MB large-table timeouts
  • the rendered page read back in a real browser: the controller API appears, and none of ActionContext / switch ctx.Action / ctx.Bind / Change / Init / SendMessage / Auto-broadcast does

🤖 Generated with Claude Code

https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

recipes/apps/chat.md's tutorial described a framework that does not exist. It
showed:

    func (s *ChatState) Change(ctx *livetemplate.ActionContext) error {
        switch ctx.Action {
        case "send":
            ctx.Bind(&data)

There is no ActionContext, no Change-with-a-switch dispatch, and no ctx.Bind.
It also defined a Init() error method that nothing calls, put the mutex and the
users map on the state struct when both live on the controller, and passed an
undefined `controller` variable to Handle. Someone following the page could not
have produced a running program.

The real examples/chat/main.go uses the controller pattern: a ChatController
singleton holding the shared data behind a mutex, a per-connection ChatState,
and named action methods dispatched by name.

Steps 2-4 are now rebuilt from that file through `include=` rather than
retyped, so they cannot drift again. The extension snippets under "Things to
add" got the same treatment — persistence now puts the store on the controller
where it belongs, and rate limiting keys on ctx.GroupID() instead of a
per-connection field a second tab would reset.

Two smaller corrections that came out of reading it closely: step 3 of "How it
works" named a SendMessage method (it is Send), and the summary claimed four
methods (there are eight).

CONTRIBUTING.md gains the rule that would have caught this — include code, do
not retype it — and a pointer to VOICE.md. That guidance used to live in
livetemplate/examples/CONTRIBUTING.md, but that repo is archived and read-only,
so this file is now the only place it can live.

Verified: voice-check green (it caught a load-bearing and four passives in my
own new prose first), tinkerdown validate 98/98 which resolves every include,
e2e green, sweep 194/196, and the rendered page read back in a real browser to
confirm the controller API appears and no trace of the invented one does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
@adnaan
adnaan merged commit 9e80ddd into main Aug 15, 2026
4 checks passed
@adnaan
adnaan deleted the chat-api branch August 15, 2026 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant