Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/livetemplate/components v0.0.0-20251224004709-1f8c1de230b4
github.com/livetemplate/livetemplate v0.8.1-0.20260118195628-f6a04e2a2d8b
github.com/livetemplate/livetemplate v0.8.2
github.com/livetemplate/lvt v0.0.0-20260110064539-b9afb9e6df26
modernc.org/sqlite v1.43.0
)
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/livetemplate/components v0.0.0-20251224004709-1f8c1de230b4 h1:wLfVleSSlcv4NPg5KN8pul0Rz9ub1CtI8OAcPlyBYlw=
github.com/livetemplate/components v0.0.0-20251224004709-1f8c1de230b4/go.mod h1:+C2iGZfdgjc6y6MsaDHBWzWGIbBHna4l+ygFYJfuyUo=
github.com/livetemplate/livetemplate v0.8.1-0.20260118195628-f6a04e2a2d8b h1:jzwam9JEm/IQpwCNHLeKD33004pjmQDdbkpmLeL/s4U=
github.com/livetemplate/livetemplate v0.8.1-0.20260118195628-f6a04e2a2d8b/go.mod h1:0jD5ccG/VQ/BmjbsZdOamAeFh+aO/f1yJeMQqhxPa68=
github.com/livetemplate/livetemplate v0.8.2 h1:nPxBIOdHy/SO3cSeVlyKZj5jtCXzC23OufI1KEylwvE=
github.com/livetemplate/livetemplate v0.8.2/go.mod h1:0jD5ccG/VQ/BmjbsZdOamAeFh+aO/f1yJeMQqhxPa68=
github.com/livetemplate/lvt v0.0.0-20260110064539-b9afb9e6df26 h1:RRYko8rFvHz8ad5ixw3ke1ZvJKMtVqJA6Ddxx92Wobw=
github.com/livetemplate/lvt v0.0.0-20260110064539-b9afb9e6df26/go.mod h1:b+qhiaDS5oURHjiCs+ZPOoJTtZ+5cCM8xyriVA97uQo=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
Expand Down
265 changes: 85 additions & 180 deletions progressive-enhancement/progressive-enhancement.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,195 +4,100 @@
<title>{{.Title}}</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 600px;
margin: 40px auto;
padding: 0 20px;
background: #f5f5f5;
}
h1 { color: #333; margin-bottom: 5px; }
.subtitle { color: #666; margin-bottom: 20px; font-size: 14px; }
.mode-indicator {
background: #e8f4f8;
border: 1px solid #b8d4e3;
border-radius: 4px;
padding: 10px;
margin-bottom: 20px;
font-size: 13px;
}
.mode-indicator.js { background: #e8f8e8; border-color: #b8e3b8; }
.flash {
padding: 12px;
border-radius: 4px;
margin-bottom: 15px;
}
.flash.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.flash.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.add-form {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.add-form input[type="text"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
margin-bottom: 10px;
}
.add-form input[type="text"]:focus {
border-color: #007bff;
outline: none;
}
.add-form input.error {
border-color: #dc3545;
}
.add-form button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.add-form button:hover { background: #0056b3; }
.error-message {
color: #dc3545;
font-size: 13px;
margin-top: -5px;
margin-bottom: 10px;
}
.todo-list {
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
overflow: hidden;
}
.todo-item {
display: flex;
align-items: center;
padding: 15px;
border-bottom: 1px solid #eee;
}
.todo-item:last-child { border-bottom: none; }
.todo-item.completed .todo-title {
text-decoration: line-through;
color: #999;
}
.todo-title {
flex: 1;
margin-left: 10px;
}
.todo-time {
color: #999;
font-size: 12px;
margin-right: 10px;
}
.todo-actions { display: flex; gap: 5px; }
.todo-actions form { margin: 0; }
.btn-toggle, .btn-delete {
padding: 5px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.btn-toggle { background: #28a745; color: white; }
.btn-toggle:hover { background: #1e7e34; }
.btn-delete { background: #dc3545; color: white; }
.btn-delete:hover { background: #bd2130; }
.empty-state {
text-align: center;
padding: 40px;
color: #999;
}
noscript .mode-indicator { display: block !important; }
/* JS/no-JS mode indicator visibility */
.js-mode { display: none; }
.no-js-mode { display: block; }

/* Completed todo item */
.completed { text-decoration: line-through; opacity: 0.6; }

/* Empty state centering */
.empty-state { text-align: center; }

/* Todo item layout */
.todo-item { display: flex; align-items: center; gap: var(--pico-spacing); }
.todo-item form { margin: 0; }
.todo-title { flex: 1; }
</style>
Comment on lines +7 to 22
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

There is a significant discrepancy between the PR description and the code changes. The PR title and description state this is a simple library update to v0.8.2 with bug fixes and fuzz testing improvements. However, the progressive-enhancement.tmpl file contains a major UI refactoring that:

  1. Adds the Pico CSS framework as a new external dependency
  2. Removes all custom CSS styling (approximately 100 lines)
  3. Changes HTML structure and class names throughout the template
  4. Modifies flash message rendering to use <ins> and <del> elements

This template refactoring appears to be unrelated to the v0.8.2 library update mentioned in the PR description. These are substantial changes that should either:

  • Be documented in the PR description if they're intentional additional changes
  • Be split into a separate PR if they're not directly related to the library update

This makes it difficult to review the PR as a "library update" when it includes significant unrelated changes.

Copilot uses AI. Check for mistakes.
</head>
<body>
<h1>{{.Title}}</h1>
<p class="subtitle">This app works with or without JavaScript enabled</p>

<!-- Mode indicator (no-JS version shown by default) -->
<noscript>
<div class="mode-indicator">
<strong>No JavaScript Mode:</strong> Using traditional HTTP form submissions with page reloads.
Each action reloads the page to show updates.
</div>
</noscript>
<div class="mode-indicator js js-mode" id="mode-indicator">
<strong>JavaScript Mode:</strong> Using WebSocket for instant updates without page reloads.
Actions update the page in real-time.
</div>
<main class="container">
<h1>{{.Title}}</h1>
<p><small>This app works with or without JavaScript enabled</small></p>

<!-- Flash messages -->
{{if .lvt.Flash "success"}}
<div class="flash success">{{.lvt.Flash "success"}}</div>
{{end}}
{{if .lvt.Flash "error"}}
<div class="flash error">{{.lvt.Flash "error"}}</div>
{{end}}

<!-- Add todo form -->
<!-- Works with JS: lvt-submit triggers WebSocket action -->
<!-- Works without JS: method="POST" + lvt-action triggers HTTP form submission -->
<div class="add-form">
<form method="POST" lvt-submit="add">
<input type="hidden" name="lvt-action" value="add">
<input
type="text"
name="title"
value="{{.InputTitle}}"
placeholder="What needs to be done?"
{{if .lvt.HasError "title"}}class="error"{{end}}
autofocus
>
{{if .lvt.HasError "title"}}
<div class="error-message">{{.lvt.Error "title"}}</div>
{{end}}
<button type="submit">Add Todo</button>
</form>
</div>
<!-- Mode indicator (no-JS version shown by default) -->
<noscript>
<mark>
<strong>No JavaScript Mode:</strong> Using traditional HTTP form submissions with page reloads.
Each action reloads the page to show updates.
</mark>
</noscript>
<mark class="js-mode" id="mode-indicator">
<strong>JavaScript Mode:</strong> Using WebSocket for instant updates without page reloads.
Actions update the page in real-time.
</mark>
Comment on lines +31 to +39
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The use of <mark> elements for mode indicators (lines 31 and 36) is semantically questionable. The <mark> element in HTML is intended to highlight text for reference purposes (like search results), not for informational notices or status indicators.

While Pico CSS may style <mark> as an informational element, using a more semantically appropriate element would improve accessibility and standards compliance. Consider using:

  • <div role="status"> for informational status messages
  • <aside> for supplementary content
  • Or keeping the previous <div> approach with appropriate CSS classes

This is especially important for screen readers, which may announce marked text differently than status information.

Copilot uses AI. Check for mistakes.

<!-- Todo list -->
<div class="todo-list">
{{if not .Items}}
<div class="empty-state">
<p>No todos yet. Add one above!</p>
</div>
{{else}}
{{range .Items}}
{{/* No explicit data-key needed - library auto-generates content-based keys */}}
<div class="todo-item {{if .Completed}}completed{{end}}">
<!-- Toggle form -->
<form method="POST" lvt-submit="toggle" style="display:inline">
<input type="hidden" name="lvt-action" value="toggle">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="btn-toggle" title="Toggle completed">
{{if .Completed}}&#10003;{{else}}&#9675;{{end}}
</button>
</form>
<span class="todo-title">{{.Title}}</span>
<span class="todo-time">{{.CreatedAt}}</span>
<!-- Delete form -->
<form method="POST" lvt-submit="delete" style="display:inline">
<input type="hidden" name="lvt-action" value="delete">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="btn-delete" title="Delete">&#10005;</button>
</form>
</div>
<!-- Flash messages -->
{{if .lvt.Flash "success"}}
<ins style="display:block; text-decoration:none;">{{.lvt.Flash "success"}}</ins>
{{end}}
{{if .lvt.Flash "error"}}
<del style="display:block; text-decoration:none;">{{.lvt.Flash "error"}}</del>
Comment on lines +43 to +46
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The use of <ins> and <del> elements for flash messages is semantically incorrect. These HTML elements are meant to represent editorial changes to a document (inserted or deleted text), not success or error messages.

For flash messages:

  • <ins> semantically means "this text was inserted into the document"
  • <del> semantically means "this text was deleted from the document"

Consider using more appropriate semantic elements:

  • For success messages: Use a <div> with appropriate ARIA attributes like role="status" or role="alert"
  • For error messages: Use a <div> with role="alert" and aria-live="assertive"

This would improve accessibility and semantic correctness.

Suggested change
<ins style="display:block; text-decoration:none;">{{.lvt.Flash "success"}}</ins>
{{end}}
{{if .lvt.Flash "error"}}
<del style="display:block; text-decoration:none;">{{.lvt.Flash "error"}}</del>
<div role="status" aria-live="polite" style="display:block; text-decoration:none;">{{.lvt.Flash "success"}}</div>
{{end}}
{{if .lvt.Flash "error"}}
<div role="alert" aria-live="assertive" style="display:block; text-decoration:none;">{{.lvt.Flash "error"}}</div>

Copilot uses AI. Check for mistakes.
{{end}}
</div>

<!-- Add todo form -->
<!-- Works with JS: lvt-submit triggers WebSocket action -->
<!-- Works without JS: method="POST" + lvt-action triggers HTTP form submission -->
<article>
<form method="POST" lvt-submit="add">
<input type="hidden" name="lvt-action" value="add">
<input
type="text"
name="title"
value="{{.InputTitle}}"
placeholder="What needs to be done?"
{{if .lvt.HasError "title"}}aria-invalid="true"{{end}}
autofocus
>
{{if .lvt.HasError "title"}}
<small>{{.lvt.Error "title"}}</small>
{{end}}
<button type="submit">Add Todo</button>
</form>
</article>

<!-- Todo list -->
<article>
{{if not .Items}}
<div class="empty-state">
<p>No todos yet. Add one above!</p>
</div>
{{else}}
{{range .Items}}
{{/* No explicit data-key needed - library auto-generates content-based keys */}}
<div class="todo-item{{if .Completed}} completed{{end}}">
<!-- Toggle form -->
<form method="POST" lvt-submit="toggle">
<input type="hidden" name="lvt-action" value="toggle">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="secondary outline" title="Toggle completed">
{{if .Completed}}&#10003;{{else}}&#9675;{{end}}
</button>
</form>
<span class="todo-title">{{.Title}}</span>
<small>{{.CreatedAt}}</small>
<!-- Delete form -->
<form method="POST" lvt-submit="delete">
<input type="hidden" name="lvt-action" value="delete">
<input type="hidden" name="id" value="{{.ID}}">
<button type="submit" class="contrast outline" title="Delete">&#10005;</button>
</form>
</div>
{{end}}
{{end}}
</article>
</main>

<!-- JavaScript for mode detection and client library -->
<script>
Expand Down