Skip to content

feat(web): fidelity closeout — detail skeleton, optimistic status, attachment rendering (HT-23) - #66

Merged
zaridan merged 2 commits into
mainfrom
feat/ht-23-fidelity-closeout
Jul 17, 2026
Merged

feat(web): fidelity closeout — detail skeleton, optimistic status, attachment rendering (HT-23)#66
zaridan merged 2 commits into
mainfrom
feat/ht-23-fidelity-closeout

Conversation

@zaridan

@zaridan zaridan commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Implemented all three HT-23 fidelity-closeout items in the worktree /Users/tjbaker/Projects/helpthread-worktrees/feat-ht-23-fidelity-closeout (branch feat/ht-23-fidelity-closeout, commit e470500):

  1. Conversation detail-view loading skeleton — new file web/src/app/(shell)/conversations/[id]/loading.tsx. Mirrors inbox/[folder]/loading.tsx's pattern (bare Skeleton + ToolbarBand primitives, no data) but shaped to the real conversation layout: toolbar band, subject-row placeholder, 3 message-band placeholders (avatar circle + line skeletons, one with the same-speaker hairline), and the 240px context-panel aside (panel-tone toolbar, avatar-circle skeleton, name/email lines, divider, one summary line).

  2. Optimistic status changes with rollback — in web/src/components/ConversationScreen.tsx, changeStatus now mirrors updateTags/updateAssignee exactly: captures previous, sets localStatus immediately, awaits setStatusAction, and rolls back + toasts on failure (dropped the old post-then-refresh startTransition/router.refresh() shape to match). The four status MenuItem onClicks now call void changeStatus(...), matching the assignee menu's pattern. The reply-to-closed reopen was already correctly handled in sendReply's success branch (it flips status only once the send is confirmed — the send itself can't be optimistic — so no change was needed there; noted this explicitly rather than silently leaving it).

  3. Minimal inbound-attachment rendering (flagged for the maintainer's visual sign-off — this is beyond the Claude Design prototype, per his explicit instruction to flag it prominently) — added AttachmentView and ThreadView.attachments?: AttachmentView[] to web/src/lib/api-types.ts, 1:1 with specs/api/agent-inbox-v1.md §2 (HT-46). Made attachments optional in the TS type (not just empty-array-default) because the task explicitly calls out that older API responses may omit the field entirely — the component guards on thread.attachments !== undefined && thread.attachments.length > 0. Added a humanFileSize() helper to web/src/lib/format.ts. Rendering is a plain pill-style list (background --ht-surface-2, radius 999 — same token composition as TagChip, no new ds component) with a paperclip icon, filename (or "Attachment" if null), and size, wrapped in an <a target="_blank" rel="noopener noreferrer"> using the signed URL exactly as given (no caching/rewriting). Only rendered on kind === 'inbound' message bands.

No new npm dependencies. web/src/components/ds/** was read but never edited.

Flagged for the maintainer's visual sign-off — the attachment rendering is an approved addition beyond the design prototype:

  1. Status optimistic-update shape: removed the pre-existing startTransition/router.refresh() wrapper entirely rather than keeping it alongside optimism, because tags/assignee (the exact pattern to mirror) use neither — they rely on the server action's own revalidatePath and don't gate on isPending. Kept consistent rather than hybrid.

  2. ThreadView.attachments made optional (attachments?: AttachmentView[]) rather than required-with-empty-array-default, even though the spec models it as always-present ([] default). Rationale: the task instructions explicitly say "If a thread has no attachments field (older API)... render nothing," which only makes sense if the type permits the field's absence — so I read "1:1 with spec §2" as shape-fidelity (same fields, same names) rather than requiring TS-level non-optionality that would fight the stated defensive-UI requirement.

  3. Attachment styling: chose a TagChip-derived pill (surface-2 background, radius 999, ink/ink-dim/ink-muted text) as "the most conservative composition" the task asked to pick between (TagChip-like vs. plain list) — it reuses only existing color/radius tokens and no new component.

  4. Did not modify sendReply's existing closed→active-on-success logic for item 2, since it already satisfies the "reply-to-closed reopen" requirement correctly (status flips only on confirmed send, which is the right semantics — the send itself isn't safe to represent optimistically before delivery is confirmed). Documented this explicitly rather than silently treating the requirement as already met without comment.

Review — 6 findings (4 actionable), fixes applied

Verification — gate exits: typecheck 0, lint 0, tests 0, web build 0, clean tree

link https://resonantiq.atlassian.net/browse/HT-23

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Conversation messages now display downloadable inbound attachments with filenames and readable file sizes.
    • Added a dedicated loading layout for conversations, including message, toolbar, and conversation-details placeholders.
  • Bug Fixes

    • Improved conversation status updates with immediate feedback, error recovery, and protection against conflicting simultaneous changes.

zaridan and others added 2 commits July 17, 2026 09:42
… attachments

Closes the last three fidelity-checklist §5 gaps for the Agent Inbox UI:

- Add conversations/[id]/loading.tsx, mirroring the inbox folder's skeleton
  pattern (ds Skeleton + ToolbarBand) but shaped to this screen's real
  layout — toolbar, subject row, message-band placeholders, context panel —
  so the swap to real content doesn't jump the eye around.
- Make conversation status changes optimistic with rollback, mirroring the
  existing tags/assignee pattern exactly: flip local state immediately,
  revert and toast on a server rejection. The closed-reopens-on-reply case
  was already covered by sendReply's on-success branch (status only flips
  once the send itself is confirmed, since the send can't be optimistic).
- Add AttachmentView + ThreadView.attachments to api-types.ts (1:1 with
  specs/api/agent-inbox-v1.md §2, HT-46), and render a minimal filename +
  size + new-tab download link on inbound message bands, composed from
  existing tokens (no new ds component). Renders nothing when the field is
  absent (pre-HT-46 API) or empty — zero layout shift.

Item 3 (inbound-attachment rendering) is a TJ-approved addition beyond the
Claude Design prototype and needs his visual sign-off before it's considered
fidelity-complete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…contract

- changeStatus now wraps the server-action await in try/catch: a rejected
  promise (network drop, unreachable server) is treated exactly like
  {ok:false} — rollback the optimistic pill and toast, instead of silently
  stranding the UI in a status the server never applied.
- Serialize changeStatus against concurrent status changes with an
  in-flight ref: a second click while one request is pending is a no-op,
  so the rollback's captured `previous` is always a server-confirmed
  value, never another in-flight call's unconfirmed optimism.
- ThreadView.attachments is now required (matching agent-inbox-v1.md §2,
  which declares the v1.1 server always emits `[]` rather than omitting
  the field), not optional — a server regression that drops the field now
  fails the type at the boundary instead of silently rendering as "no
  attachments". Mirrors customerViewedAt's required-nullable precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 86f7289b-d700-4be3-bd3f-486c97e92dec

📥 Commits

Reviewing files that changed from the base of the PR and between b4284f3 and a963344.

📒 Files selected for processing (4)
  • web/src/app/(shell)/conversations/[id]/loading.tsx
  • web/src/components/ConversationScreen.tsx
  • web/src/lib/api-types.ts
  • web/src/lib/format.ts

📝 Walkthrough

Walkthrough

The conversation screen now renders inbound attachments, formats file sizes, serializes status updates with rollback handling, and adds a dedicated loading skeleton. Thread API types now require an attachments array.

Changes

Conversation UI updates

Layer / File(s) Summary
Attachment contract and rendering
web/src/lib/api-types.ts, web/src/lib/format.ts, web/src/components/ConversationScreen.tsx
Adds the required ThreadView.attachments contract, the humanFileSize formatter, and inbound attachment links with filenames and sizes.
Serialized status updates
web/src/components/ConversationScreen.tsx
Guards concurrent status changes, applies optimistic updates, rolls back rejected or failed requests, and updates dropdown callbacks for asynchronous handling.
Conversation loading skeleton
web/src/app/(shell)/conversations/[id]/loading.tsx
Adds reusable message skeleton rows and a composed conversation page loading layout with a details sidebar.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • Helpthread/helpthread#47: Adds the backend and specification support for ThreadView.attachments and signed BlobStore URLs.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main additions: detail skeleton, optimistic status updates, and attachment rendering.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ht-23-fidelity-closeout

Comment @coderabbitai help to get the list of available commands.

@zaridan
zaridan merged commit 701418c into main Jul 17, 2026
5 checks passed
@zaridan
zaridan deleted the feat/ht-23-fidelity-closeout branch August 2, 2026 19:19
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