Skip to content

feat: add in-app feedback board#3834

Merged
senamakel merged 8 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/feedback-board
Jun 22, 2026
Merged

feat: add in-app feedback board#3834
senamakel merged 8 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/feedback-board

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds an in-app Feedback board to openhuman: a global list of every user's feedback with Reddit-style up/down voting, feature/bug submission, comment threads, board filters + sort (Hot/Top/New), and an admin-only status control.
  • New /feedback route + sidebar nav entry (with icon); available to all authenticated users.
  • Backed by backend-alphahuman's existing /feedback API via the standard apiClient (openhuman's own JWT users — not the tiny.place wallet board, and not the Agent World tinyplace_* RPC bridge).
  • Renders author display names from the backend DTO (createdByName / comment userName), gracefully falling back to a short @handle when absent.
  • i18n added across all 14 locales; Vitest coverage for the API client, vote control, comments, filters, row, and page.

Problem

openhuman had no in-app way for users to submit feature requests / bug reports or to see and vote on others' feedback. We want the tiny.place feedback experience, but for openhuman's own user/identity model.

Solution

  • Presentation/orchestration only in app/src per the architecture rules: all HTTP lives in services/api/feedbackApi.ts; components hold no business logic.
  • Data fetching uses the established useState/useEffect + request-id-guard pattern (no react-query), matching Invites/Rewards.
  • Voting is optimistic with rollback, reconciled against the server's authoritative tallies + myVote.
  • Admin status control is gated by user.role === 'admin' client-side; the server's requireAdmin is the real boundary. Moving an item to "planned" triggers GitHub-issue creation server-side and surfaces a "View issue" link.
  • Board filters use a small themed dropdown (FeedbackFilterSelect); sort is a segmented Hot/Top/New control.

Submission Checklist

  • Tests added or updated — Vitest specs for feedbackApi, FeedbackVoteControl (optimistic + rollback + retract), FeedbackComments, FeedbackFilterSelect, FeedbackItemRow (incl. admin gating + name fallback), Feedback page, and navConfig.
  • Diff coverage ≥ 80% — added Vitest specs cover the new feedback files' changed lines; the "Frontend Coverage (Vitest)" CI job enforces the exact changed-line gate.
  • N/A: Coverage matrix — net-new feature; matrix row can be added during review if required.
  • N/A: Affected feature IDs — no existing matrix IDs touched.
  • No new external network dependencies — uses the existing apiClient; no new deps.
  • N/A: Manual smoke checklist — does not touch release-cut surfaces.
  • N/A: Linked issue — no tracking issue for this work.

Impact

  • Desktop UI; purely additive (new route + nav entry). No migrations, no perf/security regressions.
  • Author names depend on the companion backend PR (feat/feedback-author-name, adds createdByName/userName). Until that ships, names fall back to @handle — no breakage.

Related

  • Closes: N/A
  • Follow-up PR(s)/TODOs: companion backend PR tinyhumansai/backend#feat/feedback-author-name (should merge/deploy first so names render). Deferred: backend vote IP rate-limit; about_app catalog entry.

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: feat/feedback-board
  • Commit SHA: b7aeb11

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: vitest run over src/components/feedback, src/pages/Feedback.test.tsx, src/services/api/feedbackApi.test.ts, src/config/__tests__/navConfig.test.ts — all green.
  • Rust fmt/check: ran via pre-push (rust:check, rust:format:check) — no Rust changes in this PR.
  • Tauri fmt/check: ran via pre-push — no Tauri changes.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: adds a new Feedback section; no existing behavior modified.
  • User-visible effect: a new "Feedback" sidebar tab and /feedback board.

Parity Contract

  • Legacy behavior preserved: N/A — net-new surface, no legacy path replaced.
  • Guard/fallback/dispatch parity checks: N/A.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None
  • Canonical PR: This one
  • Resolution: N/A

Summary by CodeRabbit

Release Notes

  • New Features
    • Added an authenticated Feedback experience at /feedback with sort, type/status filtering, pagination, and “Load more”.
    • Included voting with optimistic updates, expandable comment threads, and optional GitHub issue links.
    • Added a feedback submission form with success, moderation-rejection, and error states.
    • Added admin-only status updates per feedback item.
  • Navigation
    • Added a Feedback tab to the bottom navigation.
  • Tests
    • Added comprehensive UI and API test coverage for the Feedback experience.
  • Documentation / i18n
    • Expanded multilingual translations for the Feedback UI.

TinyPlace-style feedback board for openhuman: a global list of all users'
feedback with Reddit-style up/down voting, feature/bug submission
(LLM-moderated server-side), comment threads, board filters, and an
admin-only status control. Backed by backend-alphahuman's /feedback API
via apiClient (own JWT users, not the tiny.place wallet board). Adds the
/feedback route + sidebar nav entry, i18n across all 14 locales, and
Vitest coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9566a056-1d6c-4f36-9273-ea7f6ab84d28

📥 Commits

Reviewing files that changed from the base of the PR and between 9eb4cc8 and 1e6fe7d.

📒 Files selected for processing (1)
  • app/src/components/feedback/FeedbackFilterSelect.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/components/feedback/FeedbackFilterSelect.test.tsx

📝 Walkthrough

Walkthrough

Adds a complete Feedback board feature: TypeScript types and a feedbackApi service module, seven new UI components (vote control, status badge, filter select, admin menu, comments, item row, submit form), a paginated Feedback page with sort/filter/local-mutation logic, navigation registration, a new nav icon, and feedback translation strings across 14 language bundles.

Changes

Feedback Board Feature

Layer / File(s) Summary
Feedback types and API service
app/src/types/feedback.ts, app/src/services/api/feedbackApi.ts, app/src/services/api/feedbackApi.test.ts
Defines TypeScript contracts (FeedbackItem, FeedbackDetail, CreateFeedbackResult, etc.) and implements all six feedbackApi endpoint wrappers (listFeedback, getFeedback, addComment, submitFeedback, voteFeedback, updateStatus) with query-string building and tests.
VoteControl, StatusBadge, and FilterSelect components
app/src/components/feedback/FeedbackVoteControl.tsx, app/src/components/feedback/FeedbackStatusBadge.tsx, app/src/components/feedback/FeedbackFilterSelect.tsx, app/src/components/feedback/Feedback*{VoteControl,StatusBadge,FilterSelect}.test.tsx
Adds FeedbackVoteControl with optimistic update and rollback, FeedbackStatusBadge pill with i18n styling map, and FeedbackFilterSelect accessible dropdown with outside-click and Escape dismissal; tests cover optimistic voting, vote retraction, rollback on failure, and keyboard/pointer interactions.
FeedbackComments, FeedbackAdminMenu, and FeedbackSubmitForm components
app/src/components/feedback/FeedbackComments.tsx, app/src/components/feedback/FeedbackAdminMenu.tsx, app/src/components/feedback/FeedbackSubmitForm.tsx, app/src/components/feedback/Feedback*{Comments,AdminMenu,SubmitForm}.test.tsx
Adds FeedbackComments (stale-request-guarded load/post), FeedbackAdminMenu (status select with pending/error), and FeedbackSubmitForm (type toggle, client validation, moderation rejection display); tests cover empty state, posting, error surfacing, disabled state, and moderation outcomes.
FeedbackItemRow card
app/src/components/feedback/FeedbackItemRow.tsx, app/src/components/feedback/FeedbackItemRow.test.tsx
Composes all sub-components into a full feedback card with deterministic avatar tinting, expand/collapse comment thread, optional GitHub issue link, and admin menu gated by isAdmin; tests cover rendering, author fallback, expansion, and issue link.
Feedback page
app/src/pages/Feedback.tsx, app/src/pages/Feedback.test.tsx
Adds a paginated board page with acceptedItemMatchesFilters, sort/type/status filters, request-id stale-response guard, in-place item patching vs. reload on filter mismatch, and "Load more" append; tests cover rendering, empty/error states, vote patching, accepted submission reload gating, and admin status-change row removal.
Navigation wiring
app/src/AppRoutes.tsx, app/src/config/navConfig.ts, app/src/config/__tests__/navConfig.test.ts, app/src/components/layout/shell/navIcons.tsx
Registers an authenticated /feedback route in AppRoutes, adds the feedback entry to NAV_TABS, adds a feedback SVG case to NavIcon, and updates nav config tests to expect 6 tabs.
i18n translations (14 languages)
app/src/lib/i18n/en.ts, app/src/lib/i18n/ar.ts, app/src/lib/i18n/bn.ts, app/src/lib/i18n/de.ts, app/src/lib/i18n/es.ts, app/src/lib/i18n/fr.ts, app/src/lib/i18n/hi.ts, app/src/lib/i18n/id.ts, app/src/lib/i18n/it.ts, app/src/lib/i18n/ko.ts, app/src/lib/i18n/pl.ts, app/src/lib/i18n/pt.ts, app/src/lib/i18n/ru.ts, app/src/lib/i18n/zh-CN.ts
Adds nav.feedback and the full set of feedback.* UI strings (board states, status/type/sort/filter labels, voting, submission flow, admin, and comments) to all 14 language bundles.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FeedbackPage
  participant FeedbackItemRow
  participant FeedbackVoteControl
  participant feedbackApi

  rect rgba(100, 149, 237, 0.5)
    note over FeedbackPage,feedbackApi: Initial Load
    FeedbackPage->>feedbackApi: listFeedback(sort, typeFilter, statusFilter, page)
    feedbackApi-->>FeedbackPage: FeedbackListResult { items, total }
    FeedbackPage->>FeedbackItemRow: render item rows
  end

  rect rgba(144, 238, 144, 0.5)
    note over User,feedbackApi: Optimistic Vote
    User->>FeedbackVoteControl: click upvote
    FeedbackVoteControl->>FeedbackPage: onVoted(optimisticItem)
    FeedbackPage->>FeedbackPage: patch item in-place (no refetch)
    FeedbackVoteControl->>feedbackApi: voteFeedback(id, 1)
    feedbackApi-->>FeedbackVoteControl: serverItem
    FeedbackVoteControl->>FeedbackPage: onVoted(serverItem)
  end

  rect rgba(255, 165, 0, 0.5)
    note over User,feedbackApi: Submit New Feedback
    User->>FeedbackPage: submit form (FeedbackSubmitForm)
    FeedbackPage->>feedbackApi: submitFeedback(input)
    feedbackApi-->>FeedbackPage: CreateFeedbackResult
    alt accepted and matches active filters
      FeedbackPage->>feedbackApi: listFeedback(page 1) reload
      feedbackApi-->>FeedbackPage: updated FeedbackListResult
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • tinyhumansai/openhuman#3611: Modifies app/src/config/navConfig.ts and navConfig.test.ts to change NAV_TABS array entries, the same code surface where this PR adds the new feedback tab.
  • tinyhumansai/openhuman#3751: Touches AppRoutes.tsx and navigation shell wiring, which this PR also extends to register the authenticated /feedback route and add the feedback tab.

Suggested reviewers

  • oxoxDev

🐇 A feedback board has grown from the ground,
With votes and comments all tightly bound!
I hopped through 14 tongues with care,
Optimistic upvotes fill the air. ✨
From open to closed, each status a pill—
This rabbit reviewed it with wiggly thrill! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main addition: a feedback board feature. It is specific, directly related to the primary change throughout the PR, and appropriately highlights the most important change from the developer's perspective.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@YellowSnnowmann YellowSnnowmann marked this pull request as ready for review June 19, 2026 12:52
@YellowSnnowmann YellowSnnowmann requested a review from a team June 19, 2026 12:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b7aeb11131

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/pages/Feedback.tsx Outdated
YellowSnnowmann and others added 2 commits June 19, 2026 18:36
handleAccepted prepended a newly-accepted item and bumped the total
unconditionally, so a submission that doesn't match the active type/status
filter (e.g. a Feature while filtered to Bugs) wrongly appeared and skewed
the count. Now gated by a pure acceptedItemMatchesFilters() helper (unit
tested). Addresses the review comment on Feedback.tsx.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aths

Adds Vitest specs for FeedbackSubmitForm (submit/clear/success, moderation
reject, type toggle, error) and FeedbackAdminMenu (status change + error),
and extends FeedbackItemRow (comment-thread expand, GitHub issue link).
Raises diff coverage on the feedback changes back above the 80% gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@CodeGhost21 CodeGhost21 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review — feedback board

Clean, well-architected PR that follows project conventions closely and is unusually well-tested. All 19 CI checks pass. Findings below are mostly minor; nothing blocking.

Strengths

  • Architecture compliance is excellent. All HTTP lives in services/api/feedbackApi.ts; components are presentation/orchestration only. The ApiResponse<T>response.data unwrap and direct apiClient usage match the established inviteApi/rewardsApi pattern verbatim. Using REST apiClient rather than core_rpc_relay is correct here — this hits the backend directly like its siblings, not the Rust core.
  • i18n is complete and real — keys in en.ts plus genuine translations across all 14 locales; parity job passes, no English placeholders.
  • Thorough tests — every component covers happy path, error, and edge cases (optimistic vote + rollback + retract, moderation reject, admin gating, name fallback). The pure acceptedItemMatchesFilters helper is exported and unit-tested.
  • Good defensive patterns: request-id guards on async loads, encodeURIComponent on ids, optimistic-with-rollback voting reconciled against server tallies, rel="noreferrer noopener" on external links, namespaced debug logging on every API call/failure.

Issues & risks

1. Deploy ordering — nav tab ships to all users unconditionally (functional risk).
The /feedback route and sidebar tab are added with no feature flag and no capability gate. If this frontend deploys before the companion backend (/feedback endpoint) is live, every user gets a visible tab that lands on a load error. The PR body says the backend PR "should merge/deploy first," but nothing enforces it. Worth confirming the deploy sequence, or gating the tab behind a flag until the backend is rolled out. This is the one item I'd actively confirm before merge.

2. Comment count isn't pluralized and assembles word-order in JSX.
FeedbackItemRow.tsx: {item.commentCount} {t('feedback.comments')} renders "1 comments", and count-then-word order is hardcoded — awkward for several of the locales just added (and RTL like ar). Since everything else is fully i18n'd, an ICU-style {count, plural, ...} key would be more consistent.

3. Admin can set closed, but the status filter can't select it.
FeedbackAdminMenu offers open/planned/completed/closed, while the board's status filter (Feedback.tsx) only offers open/planned/completed. A closed item is then only visible under "All statuses". Confirm this is deliberate.

4. New accepted items are always prepended regardless of sort.
handleAccepted does [accepted, ...prev] even when sort is top/hot, so a brand-new zero-score item jumps above higher-ranked ones until the next reload. Cosmetic; self-corrects on refresh.

5. Minor. github.issueUrl is rendered straight into href with no scheme check — low risk since it's server-generated, but worth a guard if ever user-influenceable. FeedbackComments.getFeedback also loads all comments unpaginated — fine for now, could grow.

Verdict

Approve with minor follow-ups. No blocking correctness or security issues; conventions, tests, and i18n are all in order. The thing to confirm before merge is #1 (backend deploy ordering); #2–#5 are nits that can be deferred.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f6ee47757a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/src/pages/Feedback.tsx Outdated
}, [load]);

const handleItemChange = (updated: FeedbackItem) => {
setItems(prev => prev.map(item => (item.id === updated.id ? updated : item)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Drop updated rows that leave the active filter

When an admin is viewing a filtered board (for example status=open) and changes a row to completed or closed, FeedbackAdminMenu sends the updated item here, but the list only replaces the row. The item then remains visible in a query it no longer matches and the filtered total stays too high until a full reload; remove/decrement when acceptedItemMatchesFilters no longer passes or refetch the current query after status updates.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 4598d00. handleItemChange now checks acceptedItemMatchesFilters on the updated row: in-filter edits (votes, comments, in-filter status changes) are still patched in place, but once a status change pushes the row out of the active filter (e.g. open → completed while filtered to Open) it calls reload() so the row drops and the total/paging realign with the query.

Comment thread app/src/pages/Feedback.tsx Outdated
…ions

Optimistic local edits on the feedback board drifted from the filtered/
paginated query the server actually returns:

- Accepting a submission prepended it and bumped the total unconditionally,
  so when page 1 was full the next "Load more" fetched backend page 2 while
  page 1 now held the new item — duplicating/skipping rows.
- A status change only replaced the row in place, so an item moved out of the
  active filter (e.g. open -> completed while filtered to Open) stayed visible
  and kept the total too high until a full reload.

Re-anchor to the server instead of patching local state: a shared reload()
refetches page 1 after a mutation that changes membership. handleAccepted
reloads only when the new item matches the active filters; handleItemChange
patches in place for in-filter edits (votes, comments) and reloads when a
status change pushes the row out of the filter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label Jun 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
app/src/pages/Feedback.tsx (1)

114-114: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use a shared interface type for the callback payload instead of an inline object shape.

Line 114 defines an inline object type, which can drift from the API contract. Reuse CreateFeedbackResult from app/src/types/feedback (or a derived Pick) for consistency.

Proposed change
-import type { FeedbackItem, FeedbackSort, FeedbackStatus, FeedbackType } from '../types/feedback';
+import type {
+  CreateFeedbackResult,
+  FeedbackItem,
+  FeedbackSort,
+  FeedbackStatus,
+  FeedbackType,
+} from '../types/feedback';
@@
-  const handleAccepted = (result: { feedback: FeedbackItem | null }) => {
+  const handleAccepted = (result: CreateFeedbackResult) => {
     const accepted = result.feedback;

As per coding guidelines: **/*.{ts,tsx} requires using interface for object shapes in TypeScript.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/pages/Feedback.tsx` at line 114, Replace the inline object type
annotation in the handleAccepted function parameter with a shared interface type
from the feedback types module. Instead of defining the type inline as `{
feedback: FeedbackItem | null }`, import and use the `CreateFeedbackResult`
interface (or create a derived type using `Pick` if only specific properties are
needed) from `app/src/types/feedback` to ensure consistency with the API
contract and follow the TypeScript coding guidelines that require named
interfaces for object shapes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/feedback/FeedbackAdminMenu.tsx`:
- Around line 50-55: The label element for the feedback status is not
programmatically associated with the select control below it, making it
inaccessible to assistive technology. Add an id attribute to the select element
(the one with value={item.status} and the handleChange callback), and add an
htmlFor attribute to the label element with the same id value to create the
proper association between the label and the form control.

In `@app/src/components/feedback/FeedbackItemRow.tsx`:
- Around line 118-121: The onCommentAdded callback in the FeedbackComments
component passes a full reconstructed item object to onChange, which can
overwrite newer field changes if they occur before the comment post completes.
Instead of passing the entire item with an updated commentCount, modify the
callback to pass only the id and commentCountDelta (as 1) to onChange, and
update the parent component to merge this delta against the latest item state
rather than replacing the full object. This ensures concurrent updates to other
fields like vote or status are not lost.

In `@app/src/components/feedback/FeedbackSubmitForm.tsx`:
- Around line 127-145: The title input and body textarea fields in the
FeedbackSubmitForm component currently lack explicit labels and rely only on
placeholders for accessibility. Add an id attribute to both the input element
(for the title) and the textarea element (for the body), then wrap each with a
label element that uses htmlFor to reference the corresponding id, or
alternatively add aria-label attributes to each field. Use the existing
translation keys (feedback.submit.titlePlaceholder and
feedback.submit.bodyPlaceholder) as a reference for creating descriptive labels
that make the fields accessible to screen readers and maintain clarity when
users start typing.

In `@app/src/lib/i18n/ko.ts`:
- Around line 36-37: The 'feedback.submit.rejected' key in the Korean
translations file currently has a message that sounds like a generic technical
error, making it indistinguishable from 'feedback.submit.error'. Update the
Korean translation for 'feedback.submit.rejected' to explicitly indicate that
the feedback was rejected due to moderation or policy reasons rather than a
technical failure, ensuring the message clearly differentiates a rejection
decision from a system error.
- Around line 10-13: The feedback.loadMore and feedback.expand keys in the
Korean translation file (ko.ts) both use the same text "더 보기", which makes
pagination and item expansion indistinguishable to users. Change the
feedback.expand key to use a distinct Korean translation that clearly indicates
expanding or showing details of an item, while keeping feedback.loadMore as is
for pagination purposes.

In `@app/src/lib/i18n/pl.ts`:
- Line 15: In the Polish translation file, change the value for the
feedback.comments key from the genitive plural form komentarzy to the nominative
plural form Komentarze with a capital K. The current genitive form reads like a
count suffix rather than a standalone section label, so use the nominative
plural form which is more appropriate for display as a section heading on its
own.

---

Nitpick comments:
In `@app/src/pages/Feedback.tsx`:
- Line 114: Replace the inline object type annotation in the handleAccepted
function parameter with a shared interface type from the feedback types module.
Instead of defining the type inline as `{ feedback: FeedbackItem | null }`,
import and use the `CreateFeedbackResult` interface (or create a derived type
using `Pick` if only specific properties are needed) from
`app/src/types/feedback` to ensure consistency with the API contract and follow
the TypeScript coding guidelines that require named interfaces for object
shapes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e3d54b5-9974-47a4-9045-59f18f5e2153

📥 Commits

Reviewing files that changed from the base of the PR and between 6d220a7 and 4598d00.

📒 Files selected for processing (36)
  • app/src/AppRoutes.tsx
  • app/src/components/feedback/FeedbackAdminMenu.test.tsx
  • app/src/components/feedback/FeedbackAdminMenu.tsx
  • app/src/components/feedback/FeedbackComments.test.tsx
  • app/src/components/feedback/FeedbackComments.tsx
  • app/src/components/feedback/FeedbackFilterSelect.test.tsx
  • app/src/components/feedback/FeedbackFilterSelect.tsx
  • app/src/components/feedback/FeedbackItemRow.test.tsx
  • app/src/components/feedback/FeedbackItemRow.tsx
  • app/src/components/feedback/FeedbackStatusBadge.tsx
  • app/src/components/feedback/FeedbackSubmitForm.test.tsx
  • app/src/components/feedback/FeedbackSubmitForm.tsx
  • app/src/components/feedback/FeedbackVoteControl.test.tsx
  • app/src/components/feedback/FeedbackVoteControl.tsx
  • app/src/components/layout/shell/navIcons.tsx
  • app/src/config/__tests__/navConfig.test.ts
  • app/src/config/navConfig.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Feedback.test.tsx
  • app/src/pages/Feedback.tsx
  • app/src/services/api/feedbackApi.test.ts
  • app/src/services/api/feedbackApi.ts
  • app/src/types/feedback.ts

Comment thread app/src/components/feedback/FeedbackAdminMenu.tsx Outdated
Comment thread app/src/components/feedback/FeedbackItemRow.tsx Outdated
Comment thread app/src/components/feedback/FeedbackSubmitForm.tsx
Comment thread app/src/lib/i18n/ko.ts Outdated
Comment thread app/src/lib/i18n/ko.ts Outdated
Comment thread app/src/lib/i18n/pl.ts Outdated
- Associate the admin status <label> with its <select> via htmlFor/id so the
  control has an accessible name.
- Give the submit form's title/body fields programmatic sr-only labels instead
  of relying on placeholders alone.
- Bubble a comment-count delta by id (onCommentAdded) and merge it against the
  latest row in the parent, instead of emitting a reconstructed item built from
  closed-over props that could clobber a concurrent vote/status change.
- ko: distinguish loadMore/expand/collapse (all collided on "더 보기") and make
  the moderation-rejection message read as a rejection, not a generic error.
- pl: use the nominative "Komentarze" for the standalone comments label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label Jun 22, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 22, 2026
sanil-23
sanil-23 previously approved these changes Jun 22, 2026

@sanil-23 sanil-23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM ✅ — approving.

Strong, self-contained feature. The riskiest part — optimistic vote ↔ server reconciliation, filter-aware reload, and the comment-delta-by-id merge that avoids clobbering concurrent edits — is carefully handled and genuinely tested. Architecture (HTTP isolated in feedbackApi.ts, presentation-only components), i18n parity across all 14 locales, a11y basics, and path-encoding/injection safety all check out.

Leaving a few non-blocking nits inline — none gate the merge:

  • Error banner + empty state can render together on load failure.
  • expanded conflates body-expand with mounting the comments fetch.
  • Custom filter dropdown advertises listbox ARIA without arrow-key nav.

One app-wide observation (not for this PR): apiClient throws plain {success,error} objects, not Error, so the instanceof Error branches always fall back to the generic message and the backend's specific error string never surfaces. This matches the existing Invites/Rewards pattern, so it's fine to leave as-is here.

</svg>
</div>
<p className="text-sm text-neutral-500 dark:text-neutral-400">
{t('feedback.empty')}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: on a failed load, finally sets isLoading=false and items stays [], so this empty state renders alongside the error banner above — the user sees both the red error and "No feedback yet. Be the first to share an idea."

Suggest gating the empty branch on !loadError:

) : loadError ? null : (
  /* empty state */
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9eb4cc8. Gated the empty-state branch on the error: ... : items.length > 0 ? (list) : loadError ? null : (empty state), so on a failed load only the error banner shows. Added a test asserting the "No feedback yet" copy is not in the document when the load rejects.

</div>

{expanded && (
<FeedbackComments feedbackId={item.id} onCommentAdded={() => onCommentAdded?.(item.id)} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking: the "N comments" button (L96) and the "Show more/Show less" button (L110) both toggle the same expanded flag, which un-clamps the body and mounts <FeedbackComments> (firing a getFeedback request). So expanding just to read the full description triggers an unexpected comments fetch, and clicking "comments" also expands the body. Consider separate expanded/showComments flags if these are meant to be independent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good observation — and you're right that expanding to read a long body also mounts FeedbackComments and fires a getFeedback request. The single expanded flag is currently intentional (expanding an item reveals its full description and its discussion together), so I've left the behaviour as-is here rather than change the interaction model in this PR. If we want the "N comments" control and "Show more" to be independent (and avoid the comments fetch when only reading the body), splitting into expanded/showComments is the right move — happy to do that as a follow-up if you'd prefer it.


{open && (
<ul
role="listbox"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Non-blocking a11y: this advertises aria-haspopup="listbox" + role="listbox"/role="option", which implies Up/Down/Home/End option navigation, but only Escape + outside-click are handled. Options are still Tab-reachable (they're <button>s) so it works, but the ARIA contract isn't fully met — either add roving arrow-key handling or drop to aria-haspopup="menu" semantics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 9eb4cc8. Reworked FeedbackFilterSelect to honour the listbox contract: the open popover is focusable and takes focus, a single keydown handler implements Arrow Up/Down + Home/End to move a highlighted option (tracked via aria-activedescendant), Enter/Space selects, and the trigger opens with Arrow keys (Escape + outside-click already dismissed). Keeping focus on the listbox instead of roving it across the option buttons means the keys work regardless of whether it was opened by mouse or keyboard. Covered by new tests.

Addresses review feedback on the board:

- Feedback.tsx: gate the "No feedback yet" empty state on `!loadError` so it no
  longer renders alongside the error banner when a load fails.
- FeedbackFilterSelect: honour the listbox ARIA contract it advertises. The open
  popover now takes focus and tracks a highlighted option via
  aria-activedescendant, with Up/Down/Home/End to move, Enter/Space to select,
  and Escape + outside-click to dismiss; the trigger opens with Arrow keys.
  Keeping focus on the listbox (rather than roving it across option buttons)
  means every keystroke lands on one handler regardless of how it was opened.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/feedback/FeedbackFilterSelect.test.tsx`:
- Around line 77-96: The test name for the keyboard navigation test in
FeedbackFilterSelect.test.tsx claims to test ArrowUp and Home keys, but the
actual test implementation only exercises ArrowDown and End keys. Either add
fireEvent.keyDown calls for ArrowUp and Home keys with corresponding expect
statements to validate their behavior, or rename the test to accurately reflect
that it only tests ArrowDown, End, wrap-around behavior and Enter selection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d12eae7-cea8-42d0-aa29-d40418f24597

📥 Commits

Reviewing files that changed from the base of the PR and between 9833c90 and 9eb4cc8.

📒 Files selected for processing (4)
  • app/src/components/feedback/FeedbackFilterSelect.test.tsx
  • app/src/components/feedback/FeedbackFilterSelect.tsx
  • app/src/pages/Feedback.test.tsx
  • app/src/pages/Feedback.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/src/pages/Feedback.tsx
  • app/src/pages/Feedback.test.tsx
  • app/src/components/feedback/FeedbackFilterSelect.tsx

Comment thread app/src/components/feedback/FeedbackFilterSelect.test.tsx
The keyboard-nav case was named for Up/Down/Home/End but only drove ArrowDown
and End. Add the missing Home and ArrowUp assertions so coverage matches the
name (per review feedback).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@senamakel senamakel merged commit 56f9dac into tinyhumansai:main Jun 22, 2026
30 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants