Skip to content

feat!: attribute reduction — migrate all templates and Go code#225

Merged
adnaan merged 5 commits intomainfrom
attr-reduction
Apr 5, 2026
Merged

feat!: attribute reduction — migrate all templates and Go code#225
adnaan merged 5 commits intomainfrom
attr-reduction

Conversation

@adnaan
Copy link
Copy Markdown
Contributor

@adnaan adnaan commented Apr 5, 2026

Summary

  • Phase 3A+3B of the attribute reduction proposal
  • Migrates all Go generators (auto_tables.go, auto_tasks.go, page.go) to new attribute syntax
  • Updates TypeScript interactive-block.ts: button name routing, lvt-on:click, data-confirm, data-* extraction
  • Updates 60+ markdown template/example/doc files
  • Updates all unit tests and e2e test selectors
  • Bumps livetemplate v0.8.4 → v0.8.16, @livetemplate/client → 0.8.19

Attribute Changes

Old New
lvt-click="X" on buttons name="X"
lvt-click="X" on non-buttons lvt-on:click="X"
lvt-submit="X" form name="X"
lvt-data-*="V" data-*="V"
lvt-confirm="msg" data-confirm="msg" (CSP-safe)
lvt-reset-on:success lvt-el:reset:on:success
lvt-change="X" lvt-on:change="X"

Test plan

  • All unit tests pass (TestGenerate*, TestParser*, TestSkill*, etc.)
  • TypeScript client builds cleanly
  • Golden tests pass unchanged
  • E2e tests updated but not run (require browser + running server)
  • Manual browser verification with tinkerdown serve

🤖 Generated with Claude Code

adnaan and others added 2 commits April 5, 2026 22:19
- auto_tables.go: lvt-click → name, lvt-data-id → data-id,
  lvt-confirm → data-confirm, lvt-submit → name, lvt-reset-on:success → lvt-el:reset:on:success
- auto_tasks.go: lvt-click → lvt-on:click (checkbox), lvt-data-id → data-id,
  lvt-submit → name, lvt-reset-on:success → lvt-el:reset:on:success
- page.go: lvt-click → name, lvt-data-id → data-id
- interactive-block.ts: Remove checkLvtConfirm/extractLvtData imports,
  handle button[name] + lvt-on:click routing, form name/submitter routing,
  add local checkConfirm (data-confirm) and extractData (data-*) helpers
- Bump livetemplate v0.8.4 → v0.8.16, @livetemplate/client ^0.8.4 → latest
…butes

BREAKING CHANGE: All Go generators, templates, examples, docs, and tests
updated to use new attribute syntax:
- lvt-click → button name (buttons) or lvt-on:click (non-buttons)
- lvt-submit → form name
- lvt-data-* → data-*
- lvt-confirm → data-confirm (CSP-safe)
- lvt-reset-on:success → lvt-el:reset:on:success
- lvt-change → lvt-on:change

TypeScript client updated:
- Removed checkLvtConfirm/extractLvtData imports
- Button name routing + lvt-on:click for non-buttons
- Form name/submitter routing
- Local data-confirm and data-* extraction helpers

Dependencies bumped:
- livetemplate v0.8.4 → v0.8.16
- @livetemplate/client ^0.8.4 → ^0.8.19

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 5, 2026 17:33
@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

PR Review: feat! attribute reduction (Phase 3A+3B)

This is a well-scoped migration with consistent application across Go generators, TypeScript, and all template/doc files. The changes are coherent and the attribute table in the PR description makes the intent clear.

Correctness issues

handleSubmit now intercepts all form submits -- The old code gated on the presence of lvt-submit, so forms without that attribute were left alone. The new code intercepts every submit event inside the interactive block and defaults to action = "submit" when neither the submitter button nor the form has a name. Any form inside an lvt-source block that intentionally relied on normal browser submission will now silently fire a "submit" action to the server. Worth adding a guard to only handle the submit if action !== "" (i.e. require at least a form or submitter name), consistent with the old behaviour.

Stale lvt-change-data in tutorial -- cmd/tinkerdown/commands/templates/tutorial/index.md updates lvt-change to lvt-on:change correctly, but the sibling attribute lvt-change-data='{"message": "$value"}' was left in place. If that attribute is no longer recognized this will silently drop the data.

Design observations

extractData filtering is thin -- The method skips only confirm from element.dataset. Any other internal data- attributes on the element (e.g. data-tinkerdown-block, future framework internals) will be forwarded as action payload. A denylist or explicit allowlist might be more defensive.

Submit button name appears in both routing and payload -- HTML FormData includes the submit button if it has a name. So if the submitter button routes the action, its name also ends up in the data payload as an empty string. This edge case is worth documenting in the attribute reference.

No deprecation bridge -- Old lvt-click / lvt-submit syntax silently stops working. A one-release console warning when the old attributes are detected would ease upgrades for users with custom templates.

Minor

checkConfirm can be simplified:

private checkConfirm(element: HTMLElement): boolean {
  const message = element.dataset.confirm;
  return !message || window.confirm(message);
}

Coverage gap

E2e tests are updated but explicitly not run per the test plan. Manual verification before merge is necessary, especially for the handleSubmit always-fires behaviour which would not be caught by unit or golden tests.


Overall the migration is consistent and the direction (using standard HTML attributes) is the right call. The main things to resolve before merging: the handleSubmit always-fires behaviour and the stale lvt-change-data in the tutorial.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the “attribute reduction” breaking change across Tinkerdown’s generators, client event handling, docs/examples, and test selectors, aligning the repo with the updated livetemplate / @livetemplate/client APIs.

Changes:

  • Replace lvt-click / lvt-submit / lvt-data-* / lvt-confirm with name (button/form), lvt-on:click (non-button), data-*, and data-confirm across templates/docs/examples.
  • Update Go auto-generators and golden/unit/e2e tests to emit/target the new attribute syntax.
  • Update the TypeScript interactive block routing and bump Go/TS dependencies to newer livetemplate versions.

Reviewed changes

Copilot reviewed 100 out of 104 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
skills/tinkerdown/SKILL.md Updates tutorial examples + attribute reference to new syntax.
skills/tinkerdown/reference.md Updates full reference examples from lvt-* to name/data-*/lvt-on:*.
skills/tinkerdown/examples/01-todo-app.md Migrates example to name + lvt-on:click + data-id.
skills/tinkerdown/examples/02-dashboard.md Migrates refresh button to name="Refresh".
skills/tinkerdown/examples/03-contact-form.md Migrates form submit + delete buttons to new syntax.
skills/tinkerdown/examples/04-blog.md Migrates blog CRUD actions to new syntax.
skills/tinkerdown/examples/05-inventory.md Migrates inventory actions + refresh to new syntax.
skills/tinkerdown/examples/06-survey.md Migrates survey form and delete action to new syntax.
skills/tinkerdown/examples/07-booking.md Migrates booking form and cancel action to new syntax.
skills/tinkerdown/examples/08-expense-tracker.md Migrates expense tracker form + delete action.
skills/tinkerdown/examples/09-faq.md Migrates FAQ form + delete action.
skills/tinkerdown/examples/10-status-page.md Migrates status page actions + docs wording.
skill_examples_test.go Adjusts expectations for updated “Key Attributes” content.
README.md Updates attribute table to reflect new routing/confirm/data attributes.
parser.go Updates comment to reflect button-name routing / lvt-on:click.
parser_test.go Updates parser test fixtures/assertions to new attributes.
page.go Updates generated templates to emit name + data-id for actions.
page_test.go Updates page fixture to use name-based action buttons.
new_command_e2e_test.go Updates scaffold verification for new form routing attribute.
lvtsource_sqlite_e2e_test.go Updates chromedp selectors for new name/data-id attributes.
lvtsource_rest_e2e_test.go Updates REST e2e template + selectors for name="Refresh".
lvtsource_pg_e2e_test.go Updates PG e2e selectors for name="Refresh".
lvtsource_markdown_e2e_test.go Migrates markdown e2e templates + selectors for new attributes.
lvtsource_e2e_test.go Updates exec-source e2e selectors for name="Refresh".
execargs_e2e_test.go Migrates exec args e2e selectors and messages to form[name="Run"].
auto_tables_e2e_test.go Updates auto-table e2e selectors for new routing/data attributes.
action_buttons_e2e_test.go Updates action button selectors to button[name="..."].
internal/server/playground.html Updates embedded playground template examples.
docs/playground.html Updates docs playground template examples.
docs/sources/sqlite.md Updates sqlite source docs examples to new action/data/submit syntax.
docs/reference/lvt-attributes.md Updates attribute reference (event handling, data attrs, confirm, lifecycle).
docs/reference/frontmatter.md Updates frontmatter example form routing to name.
docs/guides/auto-rendering.md Updates auto-rendering examples + note from lvt-data-id to data-id.
docs/guides/go-templates.md Updates Go template examples to new action/data syntax.
docs/guides/debugging.md Updates debugging snippet to name/data-id.
docs/guides/ai-generation.md Updates AI-generation guide snippets to name on forms.
docs/getting-started/quickstart.md Updates quickstart button example to name="SayHello".
docs/llms.txt Updates LLM-facing examples/table to new attribute syntax.
docs/llm-system-prompt.md Updates system prompt examples to new attribute syntax.
docs/caching.md Updates cache refresh example to name + data-source.
docs/archive/ROADMAP.md Updates archived roadmap references to new attribute names.
docs/archive/launch-page.md Updates archived launch page examples and attribute descriptions.
docs/research/examples/llm-generated-apps/README.md Updates research examples to new form/button routing.
docs/research/examples/llm-generated-apps/01-job-tracker/index.md Migrates research example to form name="add".
docs/research/examples/llm-generated-apps/04-feedback-form/PROMPT.md Updates prompt guidance to reflect name on forms.
docs/research/examples/llm-generated-apps/04-feedback-form/index.md Migrates research example to form name="add".
docs/research/examples/llm-generated-apps/05-expense-tracker/index.md Migrates research example to form name="add".
docs/research/examples/llm-generated-apps/06-meeting-notes/index.md Migrates research example to form name="add".
docs/research/examples/llm-generated-apps/07-reading-list/index.md Migrates research example to form name="add".
docs/plans/tinkerdown-unified-design.md Updates plan examples to new attribute syntax.
docs/plans/livemdtools-enhancements.md Updates plan wording for button action routing.
docs/plans/2026-01-03-action-buttons-design.md Updates plan wording/examples for custom actions routing.
docs/plans/2026-01-01-cli-scaffolding-implementation.md Updates plan examples for new action routing.
docs/plans/2025-12-31-productivity-tools-research.md Updates plan examples to new action routing.
docs/plans/2025-12-31-missing-features-deep-dive.md Updates plan snippets to new name/data-* syntax.
docs/plans/2025-12-31-markdown-native-research.md Updates plan examples to new action routing.
docs/plans/2025-12-31-e2e-testing-strategy.md Updates plan wording for e2e attribute selectors.
docs/plans/2025-12-30-runbook-instances-design.md Updates plan examples for new attribute syntax.
docs/plans/2025-12-29-auto-rendering-lists-design.md Updates plan snippets to new action/data attributes.
docs/plans/2025-12-21-exec-args-form-design.md Updates plan docs from lvt-submit to form name="Run".
docs/plans/2025-12-15-web-components-design.md Updates plan wording for new event handling attributes.
docs/plans/2025-12-14-multi-interface-design.md Updates plan examples and inference rules to name/data-*.
docs/plans/2025-11-12-livemdtools-design.md Updates plan examples and test selector references.
docs/plans/2024-12-14-self-contained-markdown-apps-design.md Updates plan examples to name/data-* and data-confirm.
docs/plans/2024-12-14-phase2-validation-design.md Updates plan docs to new attribute names.
component_library_e2e_test.go Updates generated HTML assertions to name and data-id.
examples/two-line-todo/index.md Migrates checkbox action to lvt-on:click + data-id.
examples/team-tasks/app.md Migrates team-tasks app to new action routing + reset lifecycle syntax.
examples/markdown-data-todo/index.md Migrates markdown-data-todo app to new routing + reset lifecycle syntax.
examples/markdown-data-dashboard/index.md Migrates markdown-data-dashboard form + delete button syntax.
examples/markdown-data-bookmarks/index.md Migrates bookmarks example to new routing/data syntax.
examples/lvt-source-wasm-test/index.md Migrates refresh button to name="Refresh".
examples/lvt-source-sqlite-test/index.md Migrates sqlite test app controls to new syntax.
examples/lvt-source-rest-test/index.md Migrates rest test app refresh button to name="Refresh".
examples/lvt-source-pg-test/index.md Migrates pg test app refresh button to name="Refresh".
examples/lvt-source-file-test/index.md Migrates file source test refresh buttons to name="Refresh".
examples/lvt-source-exec-test/index.md Migrates exec source test refresh button to name="Refresh".
examples/expense-tracker/README.md Updates README to reference name="..." actions.
examples/expense-tracker/index.md Migrates expense tracker app to new routing/data syntax.
examples/exec-args-test/index.md Migrates exec-args form to form name="Run".
examples/action-buttons/index.md Migrates custom action button example to new routing/data syntax.
cmd/tinkerdown/commands/templates/basic/index.md Updates CLI template app to new routing syntax.
cmd/tinkerdown/commands/templates/dashboard/index.md Updates CLI dashboard template to new routing syntax.
cmd/tinkerdown/commands/templates/api-explorer/index.md Updates CLI API explorer template form routing to name.
cmd/tinkerdown/commands/templates/form/index.md Updates CLI form template to new routing/data syntax.
cmd/tinkerdown/commands/templates/markdown-notes/index.md Updates CLI markdown-notes template routing + reset lifecycle attribute.
cmd/tinkerdown/commands/templates/todo/index.md Updates CLI todo template to new routing/data syntax.
cmd/tinkerdown/commands/templates/tutorial/index.md Updates CLI tutorial template routing and lvt-on:change usage.
cmd/tinkerdown/commands/templates/wasm-source/test-app/index.md Updates WASM template refresh button to name="Refresh".
cmd/tinkerdown/commands/new_test.go Updates scaffold test to look for name=" routing.
client/src/tinkerdown-client.ts Updates lifecycle listener comment to new lvt-el:* syntax.
client/src/blocks/interactive-block.ts Implements name/lvt-on:* routing + data-confirm/data-* extraction.
auto_tasks.go Updates auto task block generation to new action routing/lifecycle attrs.
auto_tasks_test.go Updates auto task tests for new attribute strings.
auto_tables.go Updates auto table generation (refresh/edit/delete/add/update/cancel) to new syntax.
auto_tables_test.go Updates auto table tests for new attribute strings and confirm attribute.
auto_tables_integration_test.go Updates integration test comment to reflect new routing.
go.mod Bumps github.com/livetemplate/livetemplate and updates indirect deps.
go.sum Updates module sums consistent with Go module changes.
client/package.json Bumps @livetemplate/client dependency.
client/package-lock.json Locks @livetemplate/client@0.8.19.
Files not reviewed (1)
  • client/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +162 to +166
private handleClick(e: Event): void {
const target = e.target as HTMLElement;
const action = target.getAttribute("lvt-click");

// Standard HTML: button name routing
if (target instanceof HTMLButtonElement && target.name) {
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleClick relies on e.target being the actionable element. With delegated listeners, clicks on nested elements (e.g. <button><span>…</span></button> or children inside an element with lvt-on:click) will set target to the child and the action won’t be detected. Consider resolving the action element via closest('button[name]') and/or closest('[lvt-on\\:click]') before reading attributes.

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +177
// Standard HTML: button name routing
if (target instanceof HTMLButtonElement && target.name) {
e.preventDefault();

if (!this.checkConfirm(target)) {
this.log("Click action cancelled by user:", target.name);
return;
}

const data = this.extractData(target);
this.sendAction(target.name, data);
this.log("Click action (button name):", target.name, data);
return;
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button[name] branch always calls e.preventDefault() and returns, which will prevent form submission when the clicked button is a submit button. That makes the handleSubmit logic (including “submitter button name > form name” routing and FormData collection) unreachable for named submit buttons. Consider skipping click-handling for button[type="submit"] (or buttons with button.form != null) and letting the submit handler drive form actions.

Copilot uses AI. Check for mistakes.
1. handleSubmit: only intercept forms with explicit routing signal
   (submitter name or form name) — plain forms now behave natively
2. handleClick: add orphan button guard (target.form === null) to
   match @livetemplate/client semantics — buttons inside forms are
   handled by the submit path
3. Fix event listener leak: store bound handlers as class properties
   and remove them in destroy()
4. Clear pendingForm/pendingAction in destroy() to prevent phantom
   lifecycle events after disconnect
5. Use for...in on DOMStringMap instead of Object.entries to avoid
   intermediate tuple array allocation in extractData()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Code Review

Overall this is a clean, well-scoped migration. The attribute table in the PR description makes the changes easy to follow, and the Go/template/test coverage is thorough. A few issues worth addressing before merge.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

test single line body

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

  • check if a lvt-* attribute is already implemented in the livetemplate/client repository before reimplementing it

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Part 1/6: Code Review - Event delegation bug. The handleClick handler checks e.target directly. With event delegation, if a button contains a child node (e.g., a span or icon), clicking the child sets target to the child, not the button, and the handler silently does nothing. Fix: use closest("button[name]") and closest("[lvt-on:click]") to walk up to the nearest matching ancestor.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Part 2/6: FormData bug. In handleSubmit, when a named submit button triggers the form, the browser includes that buttons name=value pair in the FormData. The current code collects all entries, so the action name becomes a spurious key. Fix: exclude the submitters own entry when iterating formData (check if submitter instanceof HTMLButtonElement and key === submitter.name, then return).

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Retracting test comments - full review posted below.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Tinkerdown

Build data-driven apps with markdown

Animated demo showing a markdown file with YAML frontmatter and lvt attributes being served by Tinkerdown into a live interactive task manager with table, status badges, and action buttons

Tinkerdown is a CLI tool for creating interactive, data-driven applications using markdown files. Connect to databases, APIs, and files with zero boilerplate. Built on LiveTemplate.

Why Tinkerdown?

Tinkerdown replaces typical app scaffolding with a single markdown file — data sources in YAML, layout in markdown, interactions via HTML attributes.

  • One file = one app. Data connections, layout, and interactions all live in one place. No build step, no node_modules, no boilerplate.
  • AI gets it right. A single declarative file with no component tree or state management means less surface area for LLMs to misconfigure.
  • 9 data sources out of the box. SQLite, PostgreSQL, REST APIs, JSON, CSV, shell commands, markdown, WASM, and computed/derived sources. Point at existing infrastructure and get a working UI.
  • Progressive complexity. Write standard markdown — task lists become interactive, tables auto-bind to data sources. Need more control? Add HTML attributes. Need full custom layouts? Drop to Go templates. Each step builds on the last without rewriting. See the Progressive Complexity Guide.
  • Git-native and self-hosted. Plain text in a repo. Version history, search, collaboration, offline access, no subscriptions.
  • Made for disposable software. The admin panel for this sprint. The tracker for that hiring round. Software you'd never scaffold a React project for, but that's useful for days or weeks.

Quick Start

# Install
go install github.com/livetemplate/tinkerdown/cmd/tinkerdown@latest

# Create a new app
tinkerdown new myapp
cd myapp

# Run the app
tinkerdown serve
# Open http://localhost:8080

What You Can Build

Write a markdown file with a YAML source definition and a standard markdown table. Tinkerdown infers that the "Tasks" heading matches the "tasks" source and auto-generates an interactive table with add, edit, and delete:

---
title: Task Manager
sources:
  tasks:
    type: sqlite
    db: ./tasks.db
    table: tasks
    readonly: false
---

# Task Manager

## Tasks
| Title | Status | Due Date |
|-------|--------|----------|

Run tinkerdown serve and get a fully interactive app with database persistence — no HTML needed:

Screenshot showing an auto-generated expense tracker with data table, edit/delete buttons per row, and an add form — all from a markdown table and YAML source definition

Need more control? Use HTML attributes for explicit binding:

<table lvt-source="tasks" lvt-columns="title,status" lvt-datatable lvt-actions="Complete,Delete">
</table>

Key Features

  • Single-file apps: Everything in one markdown file with frontmatter
  • 9 data sources: SQLite, JSON, CSV, REST APIs, PostgreSQL, exec scripts, markdown, WASM, computed
  • Auto-rendering: Tables, selects, and lists generated from data
  • Real-time updates: WebSocket-powered reactivity
  • Zero config: tinkerdown serve just works
  • Hot reload: Changes reflect immediately

Data Sources

Define sources in your page's frontmatter:

---
sources:
  tasks:
    type: sqlite
    path: ./tasks.db
    query: SELECT * FROM tasks

  users:
    type: rest
    from: https://api.example.com/users

  config:
    type: json
    path: ./_data/config.json
---
Type Description Example
sqlite SQLite databases lvt-source-sqlite-test
json JSON files lvt-source-file-test
csv CSV files lvt-source-file-test
rest REST APIs lvt-source-rest-test
pg PostgreSQL lvt-source-pg-test
exec Shell commands lvt-source-exec-test
markdown Markdown files markdown-data-todo
wasm WASM modules lvt-source-wasm-test
computed Derived/aggregated data computed-source

Auto-Rendering

Generate HTML automatically from data sources:

<!-- Table with actions -->
<table lvt-source="tasks" lvt-columns="title,status" lvt-actions="Edit,Delete">
</table>

<!-- Select dropdown -->
<select lvt-source="categories" lvt-value="id" lvt-label="name">
</select>

<!-- List -->
<ul lvt-source="items" lvt-field="name">
</ul>

See Auto-Rendering Guide for full details.

Interactive Attributes

Attribute Description
lvt-source Connect element to a data source
name (on button) Handle click events
name (on form) Handle form submissions
lvt-on:change Handle input changes
data-confirm Show confirmation dialog before action
data-* Pass data with actions

See lvt-* Attributes Reference for the complete list.

Configuration

Recommended: Configure in frontmatter (single-file apps):

---
title: My App
sources:
  tasks:
    type: sqlite
    path: ./tasks.db
    query: SELECT * FROM tasks
styling:
  theme: clean
---

For complex apps: Use tinkerdown.yaml for shared configuration:

# tinkerdown.yaml - for multi-page apps with shared sources
server:
  port: 3000
sources:
  shared_data:
    type: rest
    from: ${API_URL}
    cache:
      ttl: 5m

See Configuration Reference for when to use each approach.

AI-Assisted Development

Tinkerdown works great with AI assistants. Describe what you want:

Create a task manager with SQLite storage,
a table showing tasks with title/status/due date,
a form to add tasks, and delete buttons on each row.

See AI Generation Guide for tips on using Claude Code and other AI tools.

Documentation

Getting Started:

Guides:

Reference:

Planning:

Development

git clone https://github.com/livetemplate/tinkerdown.git
cd tinkerdown
go mod download
go test ./...
go build -o tinkerdown ./cmd/tinkerdown

License

MIT

Contributing

Contributions welcome! See ROADMAP.md for planned features and current priorities.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

1
2
3

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Sun Apr 5 18:58:31 UTC 2026

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

CLAUDE.md
Dockerfile
LICENSE
Makefile
README.md
action_buttons_e2e_test.go
auto_tables.go
auto_tables_e2e_test.go
auto_tables_integration_test.go
auto_tables_test.go
auto_tasks.go
auto_tasks_e2e_test.go
auto_tasks_test.go
block.go
build_command_e2e_test.go
client
cmd
component_library_e2e_test.go
computed_expressions_e2e_test.go
desktop
docs
e2e_helpers_external_test.go
e2e_helpers_test.go
errors.go
errors_test.go
examples
exec_toolbar_e2e_test.go
execargs_e2e_test.go
frontmatter_config_e2e_test.go
go.mod
go.sum
headless_mode_e2e_test.go
internal
livemdtools.yaml.example
lvtsource_e2e_test.go
lvtsource_file_e2e_test.go
lvtsource_graphql_e2e_test.go
lvtsource_markdown_e2e_test.go
lvtsource_pg_e2e_test.go
lvtsource_rest_e2e_test.go
lvtsource_sqlite_e2e_test.go
mermaid_diagrams_e2e_test.go
new_command_e2e_test.go
page.go
page_test.go
parser.go
parser_golden_test.go
parser_tabs_test.go
parser_test.go
partials_e2e_test.go
pkg
presentation_mode_e2e_test.go
release.sh
schedule_e2e_test.go
search_e2e_test.go
skill_examples_test.go
skills
state.go
state_test.go
template_gallery_e2e_test.go
testdata
tests
tinkerdown.go
tutorial_nav_bug_test.go
ux_enhancements_e2e_test.go

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

NOTE: Previous test comments above can be disregarded. Full code review follows in the next comment.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

FULL CODE REVIEW (posted in sections due to environment constraints):

SECTION 1 - OVERALL: Overall this is a clean, well-scoped migration. The attribute table in the PR description makes the changes easy to follow, and the Go/template/test coverage is thorough. A few issues worth addressing before merge.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

SECTION 2 - BUG: Event delegation does not walk the DOM tree. The handleClick handler checks e.target directly. With event delegation (capture phase), e.target is the actual clicked element. If a button contains a child node (e.g., button > span), clicking the span sets target to the span, not the button, and the handler silently does nothing. The same problem applies to the lvt-on:click branch. FIX: use closest("button[name]") and closest("[lvt-on:click]") to walk up to the nearest matching ancestor instead of checking e.target directly.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

SECTION 3 - BUG: Submitter button name leaks into FormData. In handleSubmit, when a named submit button triggers the form, the browser includes that buttons name=value pair in the FormData. The current code collects all entries and sends them as the data payload, so the action name becomes a spurious key in the data. FIX: when iterating formData, skip the entry where key === submitter.name (when submitter instanceof HTMLButtonElement).

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

SECTION 4 - MINORS AND NITS: (a) for...in on dataset in extractData can iterate inherited properties - prefer Object.keys(element.dataset) as the idiomatic safe form. (b) data-confirm namespace collision risk: this attribute is used by Rails UJS and others. The old lvt-confirm was unambiguous - worth noting potential conflicts in attribute docs. (c) Nit: handlers registered as (e: Event) but cast inside body - declare method signatures with specific event types (MouseEvent, SubmitEvent) to remove casts and clarify intent.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

SECTION 5 - POSITIVE NOTES: Storing bound handlers as class fields and removing them in destroy() is a real improvement - the previous anonymous arrow functions leaked listeners on every re-mount. Inlining checkConfirm and extractData rather than importing from @livetemplate/client is the right call since the attribute names changed. Test coverage across Go unit tests, golden tests, and e2e selectors is consistent and thorough. The lvt-el:reset:on:success comment update in handleMessage keeps code and docs in sync. The event delegation bug is the most impactful item - any button whose label is wrapped in a span or icon element will silently fail after this change. Recommend fixing before merge.

@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Generated with Claude Code

1. Use closest() in handleClick to walk up from e.target — fixes
   child elements inside buttons not being detected (e.g., <button><span>)
2. Add containment check (this.element.contains) to prevent matching
   ancestors outside the block
3. Skip submitter button's name=value entry in FormData iteration
   to prevent the action name from leaking as a spurious data key
4. Switch extractData from for...in to Object.keys() to avoid
   inherited property iteration on DOMStringMap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

Code Review

Overall this is a clean, well-scoped migration. The attribute reduction is a genuine improvement — using native HTML semantics (name, data-*) over bespoke lvt-* attributes reduces cognitive overhead and plays nicer with tools that understand standard HTML.


Bug: lvt-change-data left behind in tutorial template

In cmd/tinkerdown/commands/templates/tutorial/index.md, lvt-change was correctly updated to lvt-on:change, but the companion attribute lvt-change-data was left unchanged:

<input
  type="text"
  value="{{.Message}}"
  lvt-on:change="update-message"
  lvt-change-data='{"message": "$value"}'
/>

The handleChange handler only reads lvt-on:change and does not process lvt-change-data. If this was a real feature, it needs to be migrated or removed. If it was never implemented, drop the attribute from the template.


Documentation inconsistency: lifecycle attribute syntax

docs/archive/ROADMAP.md still documents the old pattern in the lifecycle section:

- `lvt-{action}-on:{event}` - Lifecycle hooks (reset, addClass, disable, etc.)

The PR migrates lvt-reset-on:success to lvt-el:reset:on:success, so the pattern template here should be updated to lvt-el:{method}:on:{state} for consistency.


Test quality regression: TestNewCommandFormTemplate

The updated assertion is too broad — any element with a name attribute (e.g. <input name="email">) will pass it. Consider asserting <form name=" or a specific known action name from that template.


Minor: handleSubmit could silently intercept forms using name for non-routing purposes

The HTML name attribute on <form> also registers the form in document.forms[name] for legacy JS access. A form that sets name for that purpose only would be unintentionally intercepted. Acceptable trade-off given the API clarity, but worth a code comment clarifying that form[name] is the exclusive action routing signal.


Nit: checkConfirm can be one line

return !message || window.confirm(message);

What works well

  • Proper handler cleanup in destroy() — bound handlers stored as class fields and removed explicitly. The previous implementation leaked listeners.
  • closest() for click delegation — correctly handles clicks on child elements inside buttons.
  • Submitter name filtered from FormData — prevents the action name from leaking as a spurious payload key.
  • Two-pass click routing (orphan button[name] first, then lvt-on:click) cleanly handles the button/non-button asymmetry.
  • Test updates look complete across unit tests, golden tests, and e2e selectors.

🤖 Generated with Claude Code

The lvt-change-data attribute was a legacy mechanism for passing data
with change events. With lvt-on:change, the input value is sent
automatically. Use name="message" so the value flows through FormData.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude bot commented Apr 5, 2026

PR Review — feat!: attribute reduction (Phase 3A+3B)

Overall this is a well-scoped migration. The attribute table in the summary is clear, the Go generator and template changes are mechanical and consistent, and the TypeScript refactor includes a real improvement (event handler cleanup in destroy()). A few things worth addressing before merge:


Bugs / Correctness

1. lvt-on:click + e.preventDefault() on checkboxes

auto_tasks.go now uses lvt-on:click="Toggle" on checkboxes:

<input type="checkbox" ... lvt-on:click="Toggle" data-id="{{.Id}}">

In handleClick, once lvt-on:click is matched, e.preventDefault() is called unconditionally — which prevents the native checkbox toggle. The checkbox stays visually untoggled until the server pushes a DOM update. Under any meaningful latency this produces a jarring momentary flicker. The old lvt-click handler had the same issue, so this isn't a regression, but it's worth noting. A common pattern here is to not call e.preventDefault() for checkboxes and rely on the server response to reconcile state.

2. Stale lifecycle attribute syntax in lvt-attributes.md

The "Processed by @livetemplate/client" section at the bottom of the reference doc was not updated:

- - Lifecycle: `lvt-{action}-on:{event}`
+ - Lifecycle: `lvt-el:{method}:on:{state}`

This conflicts with the corrected example just above it (lvt-el:reset:on:success) and will confuse anyone reading the docs.


CLAUDE.md concern

check if a lvt-* attribute is already implemented in the livetemplate/client repository before reimplementing it

checkConfirm and extractData are reimplemented locally after removing the checkLvtConfirm / extractLvtData imports from @livetemplate/client. The version bump is significant (0.8.40.8.19). Please verify the new release doesn't already expose utilities for data-confirm and data-* extraction — if it does, we should import them rather than keep two implementations in sync.


Behaviour change worth documenting

extractData now uses element.dataset (camelCase keys)

data-user-id{ userId: "..." } via the Dataset API. The old lvt-data-* mechanism presumably passed keys verbatim. Templates currently only use data-id (no hyphens), so nothing breaks today, but multi-word data keys will arrive at the server handler with a different shape than before. Worth a sentence in the migration notes or the data-* reference section.


Minor nit

README table is missing lvt-on:click

The README attribute table now lists name (on button) for click handling but omits lvt-on:click for non-button elements (the checkbox use-case). Users landing on the README won't know the escape hatch exists.


Positives

  • Bound handler references stored as class fields so removeEventListener in destroy() actually works — fixes the memory leak that the old anonymous lambdas would have caused.
  • SubmitEvent.submitter for action resolution is clean and correctly filters the submitter's name/value pair from the data payload.
  • button.form === null check correctly separates orphan buttons (handled by click) from submit buttons inside forms (handled by submit), avoiding double-firing.
  • Comprehensive update across 60+ template/doc files with no missed occurrences that I spotted.

🤖 Generated with Claude Code

@adnaan adnaan merged commit ee4382b into main Apr 5, 2026
4 checks passed
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.

2 participants