- PlanningShowDaySeparator in planning_forms.go — returns true when date
changes between consecutive events
- PlanningDaySeparator templ: slate-50 header row with date label and
data-day-separator attribute
- PlanningEventListItem: remove redundant DateLabel column (now in separator)
- Loop uses index to call PlanningShowDaySeparator before each event row
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The server-side flush didn't eliminate the race because browser HTMX and SSE
event processing are inherently async.
Real fix: embed data-current-user-id on #discussion-tab (from DiscussionTabData.
CurrentUserID). The SSE handler now skips any event whose authorUserId matches
the current user — those messages are always delivered via HTMX (IsOwn=true,
right-aligned). SSE only appends messages from other users.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- CR-01: add id="discussion-message-list" to .divide-y; change hx-target
to #discussion-message-list so HTMX appends inside the list, not after it.
Always render the list div so the target exists even when messages is empty.
- WR-01: SSE broadcast now renders IsOwn=false for all recipients so other
users don't receive the sender's right-aligned bubble
- WR-02: add HX-Retarget/HX-Reswap headers on 422 and 500 error responses
so validation errors reach the composer form in the DOM
- WR-03: replace hardcoded rgba(128,78,236,0.10) with color-mix() using
the --color-brand-primary token
- WR-04: remove hardcoded "1 participant" subtitle (no participant count in data)
- IN-02: DiscussionMessageFromRow now accepts currentUserID uuid.UUID (matching
DiscussionMessagesFromRows) instead of a pre-computed bool
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Add IsOwn bool to DiscussionMessageView; set via user.ID comparison in
DiscussionMessagesFromRows and DiscussionMessageFromRow
- Thread currentUserID through loadDiscussionTabData and all call sites
- discussion.templ: branch message-own vs message-other on message.IsOwn
- Restore .divide-y wrapper inside #discussion-messages (discussion-sse.js
depends on it to locate the message list before appending SSE events)
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Phase 16 executor removed the .task-drag-handle div from TaskCard
during restyling. Sortable.js handle: '.task-drag-handle' had no
matching element → dragging completely non-functional.
Restores the grip element with CSS-token styling (no Tailwind).
- Replace <ul>/<li> layout with @ui.Table using fileTableHead/fileTableBody helpers
- Add .overview-section-heading header with "Files" h3 and Upload file button
- Convert FileListRow outer element from <li> to <tr class="file-row-zone">
- Convert FileDeleteConfirmFragment outer element from <div> to <tr class="file-row-zone"> with <td colspan="4">
- Add Download and Delete @ui.IconButton in FileListRow actions column
- Replace FileListEmpty with @ui.EmptyState in FilesTabFragment and UploadErrorFragment
- Convert FileRowGone from <div> to <tr> for DOM consistency
- All 9 file handler tests pass; go build ./... exits 0
- tablos.templ TasksTabFragment: add etapes as 5th argument to @KanbanBoard
- handlers_tasks.go reorder (single): capture etapes from loadTasksTabData; pass to KanbanBoard
- handlers_tasks.go reorder (batch): capture etapes from loadTasksTabData; pass to KanbanBoard
- go build ./backend/... exits 0; go test ./backend/internal/web/... -count=1 passes
- Replace header with project-card-top layout: color avatar with first char, tablo-title-zone
- Replace Discussion link/Invite button/Delete button with @ui.IconButton and @ui.Button using design token variants
- Add inline tablo-delete-zone with trash @ui.IconButton (does not use TabloDeleteButtonFragment)
- Replace metadata row hardcoded flex/hex classes with tablo-metadata-row, @ui.Badge(BadgeVariantPrimary), project-progress-track/bar
- Replace 5 tab nav <a> elements from long inline Tailwind hex classes to tab-nav-item / tab-nav-item is-active
- Wrap tab nav in class="tab-nav" replacing raw flex container
- Move @TabloDescDisplay call from persistent header into TabloOverviewTabFragment
- Remove @EtapeStrip call from TasksTabFragment (D-E01; KanbanBoard call site update deferred to Plan 03)
- Remove last #804EEC hex value from TabloTitleDisplay hover class
- Regenerated tablos_templ.go via templ generate
Merge conflict resolution had taken the old template signatures (pre-AppLayout).
Restored HEAD~1 signatures then applied the header/tab-nav restyle:
- Title row: md:text-3xl font-bold, Discussion + Invite action buttons
- Metadata row: created date, status badge, 0% progress bar
- Sticky purple-accent tab bar (Overview, Tasks, Files, Discussion, Events)
- All HTMX wiring and AppLayout wrapping preserved
- Replace plain back-link + title with structured header: title row + metadata row
- Title uses larger md:text-3xl font-bold style with inline-edit preserved
- Add Discussion and Invite action buttons in header (purple brand colors)
- Add metadata row: created date, hardcoded En cours status badge, 0% progress bar
- Replace plain slate tab nav with purple-accent tab bar (icon + label per tab)
- Tab active state: text-[#804EEC] border-[#804EEC]; inactive: text-[#667085]
- Tab bar is sticky (top-0 z-40) with horizontal scroll and hidden scrollbar
- Keep all HTMX attributes, hx-push-url, hx-target="#tab-content" logic unchanged
- tablo-title-zone, tablo-desc-zone, tablo-delete-zone elements retained
- Updated TablosDashboard signature to accept activePath and tablos for AppLayout
- Replaced old @Layout call with @AppLayout (sidebar-based shell)
- Added TabloProjectCard component with project-card grid, colored avatar, tablo-title-zone, edit/delete icon buttons
- Replaced TablosEmptyState raw HTML with @ui.EmptyState component (ui-empty-state class)
- Updated TabloDetailPage signature with activePath and sidebarTablos params
- Updated TabloNotFoundPage signature with activePath and sidebarTablos params
- Both detail pages switch from @Layout to @AppLayout
Per Google's branding guidelines, the gsi-material-button requires Roboto
Medium. Add Google Fonts preconnect + stylesheet link to AuthLayout head.
Also add display:block + 100% dimensions to the SVG inside the icon
container to prevent inline baseline gaps.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Replace Layout+Card pattern with AuthLayout("Create your account", csrfToken)
- Wire GoogleButton and AuthDivider into SignupPage body
- Replace raw <input> elements with @ui.FormField/@ui.Input design system components
- Password placeholder "12 characters minimum", autocomplete="new-password"
- Add signup-copy nav link ("Already have an account? Sign in") pointing to /login
- Preserve HTMX swap: hx-post="/signup" hx-target="#signup-form" hx-swap="outerHTML"
- Remove signupCardBody helper
- Replace Layout+Card pattern with AuthLayout("Sign in to Xtablo", csrfToken)
- Wire GoogleButton and AuthDivider into LoginPage body
- Replace raw <input> elements with @ui.FormField/@ui.Input design system components
- Add signup-copy nav link ("Don't have an account? Sign up")
- Preserve HTMX swap: hx-post="/login" hx-target="#login-form" hx-swap="outerHTML"
- Remove loginCardBody, AuthProviderButtonsBlock, AuthProviderButtonControl helpers
- Update test assertions for new GoogleButton labels ("Sign in with Google" / disabled attr)
- auth_components.templ: AnimatedBackground (35 elements, /static/logo_dark.png, no light/dark pairs), GoogleButton (a/button variant, English label 'Sign in with Google'), AuthDivider ('or' divider)
- auth_layout.templ: standalone HTML shell with .login-screen, @AnimatedBackground(), .card-wrap/.card-glow/.auth-card-shell, htmx.min.js only (no sortable/sse scripts)
- No auth.User param on AuthLayout (auth pages always unauthenticated)
- just generate exits 0, all Go tests pass
- button.css: replaced with go-backend multi-class selector version + ghost variant rules
- badge.css: replaced with go-backend pill-shape version + primary variant
- card.css: replaced with go-backend token-based header/body/footer version
- card.templ: migrated from children passthrough to typed CardProps{Header/Body/Footer}
- ui_test.go: rewrote TestCard_RendersChildren -> TestCard_RendersTypedRegions; added TestBadge_PrimaryVariant; added textComponent helper + io import
- auth_login.templ, auth_signup.templ: migrated Card usage to typed CardProps API
- tablos.templ: migrated TabloCard to typed CardProps API with extracted tabloCardBody
- planning.templ, tasks.templ, events.templ, etapes.templ: all compound button class strings updated to multi-class pattern
- go test ./... passes (all packages green)
- just generate succeeds
Gap fill: three no-infrastructure unit tests that run without TEST_DATABASE_URL or S3_ENDPOINT:
- backend/templates/files_helpers_test.go — formatBytes boundary cases (B/KB/MB/GB)
- backend/internal/files/store_unit_test.go — byteCountReader accumulation, io.ErrUnexpectedEOF
guard for small files, and MultiReader body reconstruction after 512-byte sniff
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
The raw fmt.Fprintf bypassed templ's auto-escaping pipeline and was
inconsistent with every other handler. Added TaskCardGone(taskID uuid.UUID)
to tasks.templ and updated TaskDeleteHandler to use it. Ran just generate.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Sortable.js draggable must match direct children of .sortable-column.
Using .task-card (grandchild) caused Sortable to detach it from its
.task-card-zone wrapper, breaking HTMX OOB swap targets and making
drag appear to do nothing. Changed to .task-card-zone so the full
wrapper moves, keeping id= attributes intact for HTMX round-trips.
Also removed redundant form.dispatchEvent() before htmx.trigger()
which could cause a double submit on reorder.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace htmx.onLoad (requires htmx at parse time) with native
document.addEventListener('DOMContentLoaded') + 'htmx:afterSettle'
so Sortable.js is guaranteed loaded before init runs.
Add task-count-badge-{status} wrapper IDs and updateBadges() that
recounts .task-card elements on every HTMX settle so badge counts
stay in sync after create, delete, and reorder operations.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>