Skip to content

feat(forum): traditional forum layout with post actions (#130)#131

Merged
krazyjakee merged 2 commits into
masterfrom
forum-ui-revamp
Jun 14, 2026
Merged

feat(forum): traditional forum layout with post actions (#130)#131
krazyjakee merged 2 commits into
masterfrom
forum-ui-revamp

Conversation

@krazyjakee

Copy link
Copy Markdown
Contributor

Closes #130.

Revamps the forum channel UI from a bare ListTile list into a traditional forum board with the post-management affordances people expect.

Forum index (forum_view.dart)

  • Richer rows: author avatar, post title with a pinned 📌 badge, a metadata line (by Author · created · last reply …), a content preview, and a reply-count chip.
  • Sort control: Latest activity (default), Newest, Most replies — pinned posts always float to the top — plus a live post count and pull-to-refresh.
  • Per-post actions via overflow button / long-press / right-click: Open, Edit (author), Pin/Unpin (mods), Delete (author or mods), all permission-gated.
  • Fixes the (untitled) leak: falls back to the first body line, then "Untitled post".

Thread view (thread_view.dart)

  • Per-message actions (Copy / Edit / Delete) for both the editable root post and replies, mirroring the main message view.
  • showAccordThread now returns a ThreadResult so a root edit/delete inside the thread refreshes the forum row without a full reload.

Wiring

  • accord_home_messages.dart passes canManageMessages + currentUserId into ForumChannelView.
  • accord_home_message_row.dart passes canManageMessages into showAccordThread.

Permissions reuse the existing manage_messages computation and isOwn checks; the edit/delete/pin endpoints already existed in MessagesApi.

Acceptance criteria

  • Forum index uses a richer, forum-style row (author, timestamps, reply count, badges).
  • Posts have working actions including edit and delete (permission-gated).
  • Thread view exposes per-message actions consistent with the main message view.
  • Sort/filter affordance for the post list.

Not included: tag/category filtering and a locked badge — AccordMessage/AccordChannel have no tag or per-post lock fields yet, so there's no data to drive them.

Testing

  • flutter analyze --no-fatal-infos — clean on changed files.
  • flutter test — all 274 tests pass.

🤖 Generated with Claude Code

krazyjakee and others added 2 commits June 14, 2026 00:50
Revamp the forum channel UI from a bare ListTile list into a forum-style
board with per-post management.

Forum index (forum_view.dart):
- Richer rows: author avatar, title with pinned badge, created/last-reply
  metadata, content preview, and reply-count chip.
- Sort control: latest activity (default), newest, most replies; pinned
  posts float to the top. Post count + pull-to-refresh.
- Per-post actions (overflow / long-press / right-click): open, edit
  (author), pin/unpin (mods), delete (author or mods) — permission-gated.
- Fix the "(untitled)" leak: fall back to the first body line, then
  "Untitled post".

Thread view (thread_view.dart):
- Per-message actions (copy/edit/delete) for the editable root post and
  replies, mirroring the main message view.
- showAccordThread returns a ThreadResult so a root edit/delete refreshes
  the forum row.

Wire canManageMessages + currentUserId through the callers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Cache _sorted in build() so it is sorted once per rebuild, not once
  per visible item (O(n log n) per scroll event → O(n log n) per build)
- Fix _togglePin to reassign _posts like all other mutations, instead
  of mutating the AccordMessage in place while leaving _posts unchanged
- Expose _postTitle as resolveForumPostTitle so it can be unit-tested
- Wrap ThreadView Dialog in PopScope(canPop:false) so back-nav and
  scrim taps surface a root-post edit to the caller — previously only
  the explicit close button did this
- Simplify showActions (≡ _canDelete) in _MessageLineState.build()
- Style the Delete button in confirmDeletePost with error colour so it
  reads as a destructive action
- Add forum_view_logic_test.dart: unit tests for resolveForumPostTitle
  fallback chain and ThreadResult constructors

https://claude.ai/code/session_01Qb97WPiDoXeH35NECiu4X5

Copy link
Copy Markdown
Contributor Author

Code Review — findings & fixes (commit 6880e3e)

All issues below have been fixed and pushed to the PR branch. Ranked by severity.


🔴 HIGH — Bug: thread close via scrim/back-nav silently drops root edits

File: thread_view.dart

The code comment said "a back/scrim dismiss should still surface that edit to the caller" but only the explicit × button called Navigator.pop(ThreadResult.edited(_root)). Tapping the scrim or pressing the Android back button returned null, so any root-post edit made inside the thread was lost when the user dismissed without using the close button.

Fix: Wrapped the Dialog in PopScope(canPop: false) and extracted a _popWithResult() helper used by both the close button and the onPopInvokedWithResult callback. All dismissal paths now propagate the result consistently.


🔴 HIGH — Performance: _sorted called O(n) times per scroll event

File: forum_view.dart

_sorted is a getter that allocates a new list and runs an O(n log n) sort every time it is called. The ListView.separated builder called it once for itemCount and once more for each item as it scrolled into view:

itemCount: _sorted.length,      // sort #1
itemBuilder: (ctx, i) {
  final post = _sorted[i];      // sort #2 … #N+1

For a forum with 50 posts, scrolling through all items triggered 51 separate sorts.

Fix: Assign _sorted to a local sorted variable at the top of build(). The closure captures it so every itemBuilder call reuses the same already-sorted list.


🟠 MEDIUM — Bug: _togglePin mutates AccordMessage in place, bypasses _posts

File: forum_view.dart

Every other mutation (_editPost, _deletePost, _openPost) replaces the entry in _posts by spreading a new list. _togglePin instead mutated the object's field directly and left _posts pointing at the same list — inconsistent and fragile if the same object is referenced from a snapshot or the sorted closure.

Fix: Now follows the same [...?_posts] → mutate → _posts = list pattern used everywhere else:

setState(() {
  final list = [...?_posts];
  final i = list.indexWhere((m) => m.id == post.id);
  if (i >= 0) list[i].pinned = !post.pinned;
  _posts = list;
});

🟠 MEDIUM — UX: destructive "Delete" button unstyled in confirmation dialog

File: thread_view.dart (confirmDeletePost)

The delete confirmation used a plain TextButton for the destructive action, indistinguishable from "Cancel". Convention (and the destructive: true flag already used in the context menu) is to render it in the theme's error colour.

Fix: Added style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.error).


🟡 LOW — Redundant showActions variable always equals _canDelete

File: thread_view.dart (_MessageLineState.build)

// _canDelete = widget.isOwn || widget.canManageMessages
final showActions = widget.isOwn || _canDelete;
// = widget.isOwn || widget.isOwn || widget.canManageMessages = _canDelete

showActions was always identical to _canDelete. Removed the intermediate variable; the if (_canDelete) guard on the actions IconButton now reads directly.


🟡 LOW — _postTitle unexposed prevented unit testing

File: forum_view.dart

The title-fallback chain (title → first body line → "Untitled post") was the explicit fix called out in the PR description, but as a _-private function it could not be exercised from tests.

Fix: Renamed to resolveForumPostTitle (no underscore). All three call sites updated.


🟢 TESTS — Added test/features/messaging/forum_view_logic_test.dart

New test file covering:

  • resolveForumPostTitle — 8 cases: title present, whitespace trimming, null/empty/whitespace title, first-body-line fallback, all-blank-lines fallback, non-String title field
  • ThreadResult constructors — edited carries the root and deleted=false; deleted has root=null and deleted=true

🤖 Generated with Claude Code


Generated by Claude Code

@krazyjakee krazyjakee merged commit 208cedd into master Jun 14, 2026
2 checks passed
@krazyjakee krazyjakee deleted the forum-ui-revamp branch June 14, 2026 00:11
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.

Forum UI needs a revamp: no post actions (edit/delete/reply), traditional forum layout

2 participants