Repository navigation
feat(dashboard): deprecate /profile and redirect it to /app - #461
Conversation
Lunaria Status Overview🌕 This pull request will trigger status changes. Learn moreBy default, every PR changing files present in the Lunaria configuration's You can change this by adding one of the keywords present in the Tracked Files
Warnings reference
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## refactor/v3-automod #461 +/- ##
========================================================
- Coverage 50.53% 38.84% -11.70%
========================================================
Files 354 356 +2
Lines 7365 7324 -41
Branches 1530 1508 -22
========================================================
- Hits 3722 2845 -877
- Misses 2478 3256 +778
- Partials 1165 1223 +58
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
75550b2 to
b846545
Compare
961b6d3 to
7c580aa
Compare
b846545 to
7de534f
Compare
7c580aa to
a703fdf
Compare
7de534f to
8db6fea
Compare
8137a77 to
db936c2
Compare
8db6fea to
260a60f
Compare
|
3 findings · reviewed 🟠
|
There was a problem hiding this comment.
ℹ️ No critical issues — one small test gap.
Reviewed changes
Reviewed all 79 files across both commits. Locale JSON is generated and only shows key removals and "" placeholders, as expected.
- Redirects:
nuxt.config.tsanswers/profileand/accountwith a 301 to/app, setsrobots: falseand drops both from the sitemap.auth.redirects.guest/authenticatedand the guest-only rules for/oauth/loginand/loginnow land on/app. app/pages/profile.vueshim: reduced to a spinner plusnavigateTo("/app", { replace: true })so a client-side navigation from a cached link still resolves.layout: falseandviewTransition: falsefollow the redirect-on-mount trap.- Preferences dialog:
AppPreferences(moved from the profile page),AppPreferencesDialogandusePreferencesDialog(), mounted once and lazy-loaded inapp/app.vue. Opened from the header Settings button,UserMenu,HeaderAuth's dropdown, the dashboard sidebar and the bottom nav "You" tab. - Dashboard rail:
GuildServerRailemitsbrowseinstead of linking out, and the layout maps it togoToGuild(null), which respects the staged-changes guard. - Removals:
ProfileHeader,ProfileAvatar,guild/cards.vue, the profile story, the profile OG-image e2e case and snapshot, and the unusedprofile.*/header.profile/user_menu.profilekeys. I greppedapp/and found no remaining consumers of the removed keys or components. - Docs:
AGENTS.mdand the view-transition type table now match the code. - Scope: the
BottomNavslug change frommoderationtoautomodbelongs to the auto-moderation work rather than the/profiledeprecation. It looks correct againstdashboard-nav.ts, just a note for the stack.
ℹ️ The 301 redirects have no test
Nothing in the diff asserts the new route rules. The PR description shows a manual curl result, but /profile and /account are not covered by any e2e or unit test, so a later routeRules edit could silently drop the redirect. The test change was limited to removing the profile OG-image case.
Technical details
# Cover the /profile and /account redirects
## Affected sites
- `nuxt.config.ts:306-307` — new `redirect: { to: "/app", statusCode: 301 }` route rules with no test.
- `test/e2e/` — no existing redirect spec to extend (grep for `redirect` / `/profile` / `/account` returns nothing).
## Required outcome
- A Playwright (or `@nuxt/test-utils`) check that a document request to `/profile` and to `/account` returns a 301 with `location` ending in `/app`, without following the redirect.claude-sonnet-5-5 | 𝕏
db936c2 to
297f4e8
Compare
260a60f to
2457b9e
Compare
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the delta since the prior Pullfrog review: the last remaining /profile links in the dashboard now open the preferences dialog.
- Sidebar gear opens the dialog:
Sidebar.vuereplacesto="/profile"with@click="openPreferences"fromusePreferencesDialog(). - Bottom nav "You" tab opens the dialog:
BottomNav.vueusespick: openPreferencesinstead ofnavigateTo("/profile").open()takes no arguments, so passing it directly as a click handler is safe. - Tests:
BottomNav.spec.tsasserts that the "You" tab setsisOpentotrue, and its expected tab labels now match theautomodslug and its "AutoMod" short label.
The 301 route-rule test gap from the earlier review is unchanged.
claude-sonnet-5-5 | 𝕏
2457b9e to
31a1623
Compare
297f4e8 to
0a58356
Compare
|
Run failed. View the logs →
|
`/profile` duplicated what `/app` now does (list the servers you manage) and was the only place to change theme, language and reduced motion. - `/profile` and its `/account` alias answer with a 301 to `/app`. The page file stays as a shim so a client-side navigation from an old link still resolves. - The preferences move to a dialog (`AppPreferences` in `AppPreferencesDialog`), opened from the header's Settings button for guests and from the user menus when signed in. It is mounted once in `app.vue` and loaded on first open, so guests keep the controls without a page to host them. - Sign-in and guest-only redirects land on `/app`, which shows the server picker when no guild is selected. The server rail's "all servers" button and a sidebar entry go back to that picker instead of leaving the dashboard. - Removed with the page: the profile header and avatar, the guild cards grid with its search and sort, the page's story and OG snapshot, and 32 locale keys nothing references any more. Co-authored-by: Claude <noreply@anthropic.com>
… tab bar The gear of the sidebar and the last tab of the phone bar open the preferences dialog instead of the deprecated /profile page.
09b7bd4 to
6d46917
Compare
ad4f6c4 to
67278d5
Compare
Dismissed because a newer commit was pushed; Greptile will re-review the current head.
There was a problem hiding this comment.
ℹ️ No critical issues — two minor suggestions inline.
Reviewed changes
Reviewed the two new commits since the prior Pullfrog review (the branch was rebased, so they were read directly): the two-pane settings dialog and the rebuilt logs section.
- Two-pane settings dialog:
PreferencesDialog.vuenow uses theUModal#contentslot with a section list (My account, Appearance, Notifications, Language, Connections) and a page pane that collapses to list→detail belowmd. Guests only see Appearance and Language.AppPreferencesgained anonlyprop so each page renders one group. - Account page: avatar and banner, masked email, a copy button for the Discord id, an "Edit on Discord" link and a sign-out entry, all behind
account, which isnullfor guests. - Logs shell and tables: new
GuildLogsShell(search, filter slot, refresh, empty state, pager) andGuildLogsPill.CommandLogTable,DashboardActivityTableandModerationLogTabledropUTablefor ARIA grid tables plus a mobile card list, with server-side paging atLOGS_PAGE_SIZE = 10and page reset on search/filter change. - Tab counts:
Panel.vueshows a count next to each tab from fourlimit: 1requests. - Moderation tones:
MODERATION_VARIANT_MAPmarksKickandVoiceKickas errors and drops the undo entries; undo cases are forced to the success tone in the table. A new test covers the row/card duplication, permanent bans and paging.
The earlier thread on General.vue:169 (the scrollable .activity-table is not keyboard focusable) is unchanged and still open, as is the 301 route-rule test gap from the first review.
claude-sonnet-5-5 | 𝕏
| {{ ts("dashboard.coming_soon.badge") }} | ||
| </UBadge> | ||
| <p class="max-w-md text-[0.8125rem] leading-normal text-muted"> | ||
| {{ ts("dashboard.coming_soon.body", { name: current.label }) }} |
There was a problem hiding this comment.
The Notifications and Connections pages reuse dashboard.coming_soon.body, which reads "Notifications already works in the bot. Its settings will be editable here in a later update." That is a claim about a feature that does not exist, and COPY.md bans "coming soon" and promises without a source. Either drop these two entries until there is something behind them, or give them their own neutral copy (for example, "Nothing to set here yet") instead of the bot-feature sentence.
| // every answer, and the tab that is open shares this request's cache key | ||
| // family with its own table. | ||
| const guildId = computed(() => guildData.value?.id); | ||
| const moderation = useModerationLog({ guildId, limit: 1 }); |
There was a problem hiding this comment.
The comment says the open tab shares a cache key family with its own table, but the keys differ by limit (...:1:0:... here, ...:10:0:... in the table), so nothing is shared. Opening Logs fires these four count requests on top of the active table's own request, against rate-limited log endpoints. Fix the comment to say the counts are separate requests, or derive the active tab's count from its table's total.
| switch (type.toUpperCase()) { | ||
| case "USER": | ||
| return { label: ts("guild_logs.command_types.user"), tone: "success" }; | ||
| case "MESSAGE": | ||
| return { label: ts("guild_logs.command_types.message"), tone: "warning" }; | ||
| default: | ||
| return { label: ts("guild_logs.command_types.slash"), tone: "primary" }; |
There was a problem hiding this comment.
The bot stores commands picked from Discord’s menu as CONTEXT_MENU, but the new commandType() mapping treats that value as Slash. The bot’s writer uses this value too. Admins therefore see menu commands explicitly labeled as Slash commands; the row formatter also retains the misleading / prefix before their names.
Handle CONTEXT_MENU separately so these entries are labeled as menu commands and keep their names without /. This is non-blocking.
Artifacts
- Rendered the base component with the same context-menu and chat-input records, showing the existing slash prefix and absence of a Type column.
- Rendered the head component with the same records, showing that the context-menu entry is incorrectly labeled Slash.
Desktop command history before the PR
- Captured the base component at a verified 1360-pixel iframe width, showing that the context-menu name already has a slash prefix.
Mobile command history before the PR
- Captured the base component at a verified 390-pixel iframe width, preserving the same records for comparison.
Desktop command history at head
- Captured the executed head component with the context-menu record and chat-input control, showing the incorrect Slash classification.
Mobile capture of command history at head
- Captured the head component after requesting a mobile viewport; the initial capture used Vitest's scaled preview, so final full-scale mobile capture verification remains pending.
- Recorded the actual before-run command, working directory, exit code, rendered text, and refined iframe dimensions, confirming successful base execution.
- Recorded the successful head browser run and observed rendered text, confirming that CONTEXT_MENU appears as Slash.
- Captured git and source-inspection commands for the pinned writer, read-only listener, API serializer, and component diff, confirming the realistic stored command type.
- Captured the initial post-execution git check with unchanged head and no tracked diff, confirming that validation did not modify production sources.
- Contains the executed Nuxt component test, realistic fixtures, and assertions for the supplied context-menu finding.
- Contains the executed Chromium configuration and capture command, including the subsequent full-scale iframe refinement.
- Contains the executed runner that records command outputs, extracts the base component without changing checkout, and saves the comparison recordings.
- Contains the exact component extracted from base 67278d5 and executed in the comparison harness.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/components/guild/logs/CommandLogTable.vue
Line: 168-174
Comment:
**Menu commands look typed**
The bot stores commands picked from Discord’s menu as `CONTEXT_MENU`, but the new `commandType()` mapping treats that value as Slash. The [bot’s writer](https://github.com/wolfstar-project/wolfstar/blob/HEAD/src/listeners/commands/contextMenuCommandSuccessCommandLog.ts) uses this value too. Admins therefore see menu commands explicitly labeled as Slash commands; the row formatter also retains the misleading `/` prefix before their names.
Handle `CONTEXT_MENU` separately so these entries are labeled as menu commands and keep their names without `/`. This is non-blocking.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Dismissed because a newer commit was pushed; Greptile will re-review the current head.
There was a problem hiding this comment.
ℹ️ No critical issues — one accessibility regression inline.
Reviewed changes
Reviewed the single new commit since the prior Pullfrog review (957d3b55), which moves the log tables onto UTable and UPagination.
- Log tables on
UTable:CommandLogTable,DashboardActivityTableandModerationLogTablebuild arowsarray and render it throughUTablecolumns (h()cells,logsColumnWidth()meta). The mobile card list is unchanged. - Shared table styling:
LOGS_TABLE_UIandlogsColumnWidth()inapp/utils/logs-table.tscarry the frame, header tint, fixed layout and per-column widths. The moderation table drops the duration column for warnings. - Pager:
Shell.vueswaps the hand-built previous/next buttons forUPagination(shown only whentotal > pageSize), and the now-unusednext_page,previous_page,pages_ariaand filter-match keys are pruned from the locales and schema. - Tests:
ModerationLogTable.test.tsassertstable tbody tr, the column headers, the warnings-only layout and the page buttons.
The open threads on PreferencesDialog.vue:180 and Panel.vue:53, the General.vue keyboard focus thread, and the 301 route-rule test gap are not touched by this commit and remain open.
claude-sonnet-5-5 | 𝕏
| <UTable | ||
| :data="rows" | ||
| :columns="columns" | ||
| :aria-label="tableLabel" |
There was a problem hiding this comment.
UTable sets inheritAttrs: false and binds $attrs to its root <div>, so this aria-label no longer reaches the <table>. The previous role="table" wrapper carried the name, so screen reader users now get an unnamed table and axe may flag a label on a role-less div. The same applies to CommandLogTable.vue:27 and DashboardActivityTable.vue:18. Pass the text through UTable's caption prop instead; it renders a <caption> inside the table, and ui.caption can hide it visually.
Technical details
# Name the log tables
## Affected sites
- `app/components/guild/logs/ModerationLogTable.vue:29` — `:aria-label="tableLabel"` lands on the root div.
- `app/components/guild/logs/CommandLogTable.vue:27` — same.
- `app/components/guild/logs/DashboardActivityTable.vue:18` — same.
## Required outcome
- Each rendered `<table>` has an accessible name equal to its tab label.
## Suggested approach
- Replace `:aria-label` with `:caption` on each `UTable` and add `caption: "sr-only"` to `LOGS_TABLE_UI` in `app/utils/logs-table.ts`.
- Optionally assert `table caption` text in `ModerationLogTable.test.ts`.There was a problem hiding this comment.
ℹ️ No critical issues — one minor accessibility note inline.
Reviewed changes
Reviewed the two new commits since the prior Pullfrog review (957d3b55): the logs pager now shows two arrows around a "1 / 3" position label.
- Pager restyle:
Shell.vuekeepsUPaginationfor the paging logic but sets:sibling-count="0", hides the first, last and ellipsis slots, and renders only the current page's#itemslot as alogs-page-label. Scoped styles turn the prev/next buttons into 36px bordered squares on the--console-*tokens. - Narrowed pager item: the
#itemslot checksitem.type === 'page'before readingitem.value, which fixes the type error from the previous commit. - Test:
ModerationLogTable.test.tsasserts the visible1 / 3label and that no numbered page buttons remain.
The open threads on ModerationLogTable.vue (UTable aria-label landing on the root <div>), PreferencesDialog.vue:180, Panel.vue:53, the General.vue keyboard focus thread and the 301 route-rule test gap are not touched by these commits and remain open.
| View workflow run | Using claude-sonnet-5-5 | 𝕏
claude-sonnet-5-5 | 𝕏
| }" | ||
| > | ||
| <template #item="{ item, page: currentPage, pageCount }"> | ||
| <span |
There was a problem hiding this comment.
Because #item renders with as-child, reka's PaginationListItem stamps aria-label="Page 1" and aria-current="page" onto this <span>. Screen readers therefore get "Page 1" while the visible text is "1 / 3", so the page total is never announced, and the label is a generic element carrying aria-label. Consider setting an explicit aria-label from a locale string such as "Page 1 of 3" on the span, and confirm axe stays clean for it in test/nuxt/a11y.spec.ts.
| }, | ||
| formatTimeAgo(new Date(row.original.timestamp)), | ||
| "span", | ||
| { class: "leading-[1.45]" }, |
There was a problem hiding this comment.
The new desktop action cell inherits whitespace-nowrap, while LOGS_TABLE_UI adds overflow-hidden. Long descriptions now get cut off instead of wrapping, with no tooltip showing the missing text. Scrolling the table horizontally does not reveal the text clipped inside the cell. This is non-blocking, but admins cannot read the full changed settings and values. Add whitespace-normal to this cell.
Artifacts
- Chromium renders long activity entries at two desktop panel widths and scrolls the narrow table fully right, showing that the original descriptions wrap and remain readable.
- Chromium renders the same entries and exercises scrolling and hover, showing that long descriptions remain clipped without a full-text disclosure.
Original activity table at maximum horizontal scroll
- The pre-conversion table is captured at the narrow desktop width after scrolling fully right, showing wrapped action descriptions.
PR461 activity table at maximum horizontal scroll
- The PR461 table is captured at the same width and scroll position, showing that horizontal scrolling does not recover clipped action text.
- The successful command captured measured cell widths, computed styles, scroll limits, hover results, and checkout checks, confirming the same clipping in both PR heads.
- The script loads component source directly from local commits and captures runtime measurements and recordings; its final declaration-generation option adjustment was not rerun.
- The harness mounts the selected commit’s activity component with real Nuxt UI components and application CSS, exercising the desktop rendering path.
- The fixtures supply long resolved role and channel names plus a short control entry, reproducing clipping without database or authentication access.
- The shell command launches the Chromium validation with the installed browser cache and records command, working directory, and exit code in the execution log.
Ran code and verified through T-Rex
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/components/guild/logs/DashboardActivityTable.vue
Line: 166
Comment:
**Activity details get cut off**
The new desktop action cell inherits `whitespace-nowrap`, while `LOGS_TABLE_UI` adds `overflow-hidden`. Long descriptions now get cut off instead of wrapping, with no tooltip showing the missing text. Scrolling the table horizontally does not reveal the text clipped inside the cell. This is non-blocking, but admins cannot read the full changed settings and values. Add `whitespace-normal` to this cell.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| <UBadge color="neutral" variant="subtle" class="font-semibold"> | ||
| {{ ts("dashboard.coming_soon.badge") }} | ||
| </UBadge> |
There was a problem hiding this comment.
Settings promise an unstated date
The new Notifications and Connections placeholders display “Coming soon.” COPY.md explicitly bans that phrase in favor of “in development,” because nobody has set a date. Use compliant wording before merging to satisfy the repository’s copy requirement.
Context Used: CLAUDE.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/components/app/PreferencesDialog.vue
Line: 176-178
Comment:
**Settings promise an unstated date**
The new Notifications and Connections placeholders display “Coming soon.” `COPY.md` explicitly bans that phrase in favor of “in development,” because nobody has set a date. Use compliant wording before merging to satisfy the repository’s copy requirement.
**Context Used:** CLAUDE.md ([source](https://github.com/wolfstar-project/wolfstar.rocks/blob/main/CLAUDE.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| size="sm" | ||
| @click="copy(row.copy)" | ||
| > | ||
| {{ copied ? ts("common.copied") : ts("profile.copy") }} |
There was a problem hiding this comment.
The new Discord ID button is named only “Copy,” so a screen reader’s button list does not say what it copies. COPY.md requires verb-plus-object labels and bans bare verbs. Label it “Copy Discord ID,” or give it that accessible name, before merging to satisfy this requirement.
Context Used: CLAUDE.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: app/components/app/PreferencesDialog.vue
Line: 145
Comment:
**Copy button lacks context**
The new Discord ID button is named only “Copy,” so a screen reader’s button list does not say what it copies. `COPY.md` requires verb-plus-object labels and bans bare verbs. Label it “Copy Discord ID,” or give it that accessible name, before merging to satisfy this requirement.
**Context Used:** CLAUDE.md ([source](https://github.com/wolfstar-project/wolfstar.rocks/blob/main/CLAUDE.md))
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…454) ### 🔗 Linked issue Part of the stack superseding #422. Depends on #453. **Stack:** #451 foundation ← #452 header/footer ← #453 marketing ← #454 `/app` dashboard ← #455 V7 schema + Prisma ORM 8 (draft) ### 🧭 Context The dashboard becomes one client-rendered page that keeps the active guild in state instead of the URL. Unlike #422 this layer does not depend on the V7 schema: it runs against the current database and typechecks. ### 📚 Description `app/pages/app.vue` renders the picker, the section components and the logs panel inside the dashboard layout, which gains a server rail, a Modules summary card and grouped sidebar navigation. `useActiveGuild()` holds guild, section and logs tab; `useDashboardNavigation()` parks any switch behind the unsaved-changes dialog while edits are staged. The three guild stores are keyed per guild, and the save handler captures the guild id before awaiting so a late PATCH response cannot clear another guild's draft. The selection is mirrored to localStorage so a reload lands on the same server. `/guilds/:id/manage/*` and `/guilds/:id/logs/*` stay as shims that record the target into state and forward to `/app`. Route rules: `/app` is `ssr: false`, not prerendered, out of the sitemap and `robots: false`; `/guilds/**` is `robots: false` as well (new here, the auth-gated shims were indexable before). `@vueuse/router` has no remaining usage and is removed; the lockfile diff is limited to that package. Open question: the shims render a spinner on the server and redirect in the browser because the guild id has to land in client state. A route middleware would be cleaner but cannot write that state. Fine with the current shape unless someone has a better idea. ### 🎨 Console layout The dashboard now follows the console redesign (rail, sidebar, title bar, cards), with the phone, foldable and tablet variants. - **Shell.** A server rail, a sidebar whose sections are grouped under Modules and Records, a 48px title bar with the unsaved-changes pill, and a docked save bar. On phones the sidebar and the rail move into a drawer and the main sections get a tab bar. - **Sidebar.** Sections can be pinned and the ones opened last appear as shortcuts. Both are browser-local (`wolfstar-dashboard-nav`) and never reach the server. `app/utils/dashboard-nav.ts` is the single list the sidebar, the tab bar and the page title read. - **Overview.** Counts of the server, the moderation actions of the last seven days as a chart, the module list and the activity feed in cards. Everything shown is read from the existing log and guild routes; the chart is bucketed in the browser from one page of the moderation log (its total is the log's own, so it stays exact past 100 actions and the chart says so). - **Surfaces.** Rail, sidebar, cards and page use the design system's ink scale (`--ink-*`, added next to the spectrum tokens) in the dark theme and white with a tinted rail in the light one. `console-card` and `console-label` are the shared utilities. Later layers adapt what they own: #455 lays the settings sections out as cards, #457 the auto-moderation rules, #461 swaps the `/profile` links for the preferences dialog. ###⚠️ Not in the design The mockup also has Auto-Delete and Auto-Purge pages, a "Prisma 8 contract" badge and an open-reports count. Auto-Delete and Auto-Purge are not here: the tables exist in the contract but the dashboard has no routes for them, so they need their own layer. The badge and the open-reports count are mock data with nothing behind them. > 🤖 AI disclosure: [Wolfstar Agent Kit](https://github.com/wolfstar-project/wolfstar-agent-kit) modified this description. [My AI open-source policy](https://harlanzw.com/blog/ai-in-open-source). <!-- greptile_comment --> <!-- greptile_summary --> <h2><a href="https://app.greptile.com/api/retrigger?id=78359990"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/RetriggerDark.svg?v=2"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"><img alt="Retrigger" src="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2" align="right"></picture></a>Confidence Score: 4/5</h2> <!-- greptile-risk --> Do not merge until the outstanding settings-edit loss in previous comment 1 is fixed; the two new navigation and activity-display concerns are non-blocking. <h2>T-Rex evidence</h2> **[Evidence from the check](https://app.greptile.com/trex/artifacts/747f3009-3e3d-45fe-bf44-75536b9ebbe8)** - The component test exercises both real card variants with Ctrl, Meta, and ordinary clicks using actual composables; its latest event-observation revision still needs execution. **[Evidence from the check](https://app.greptile.com/trex/artifacts/dd50729a-d872-4d2b-bb7a-bb04b6ddcd5c)** - The configuration launches Chromium through Playwright and supplies click, viewport, video, and poster capture commands; the latest commands still need a completed run. **[Evidence from the check](https://app.greptile.com/trex/artifacts/10446552-fa3f-41b7-a293-b53f0b1f41f4)** - The card was extracted with git show from the specified base and rendered without editing tracked source; this is the executed baseline component. **[Command output from the check](https://app.greptile.com/trex/artifacts/616e8d30-a318-4665-8720-c9f50d4504f9)** - The last completed base run captured unchanged original-tab guild, section, and route after Ctrl-click, then failed because no new tab was observed; this proves only the baseline state behavior. **[Command output from the check](https://app.greptile.com/trex/artifacts/5ae81b80-790f-48a6-87ca-3418dc486b4d)** - Git show captured numbered card and composable source from head and all four requested higher snapshots; the inspected paths retain the unconditional click handler. **[Command output from the check](https://app.greptile.com/trex/artifacts/a0076d94-fec5-4a45-9d22-791ac7467f71)** - The initial Chromium run failed because the real tooltip lacked its provider; the harness subsequently added the provider and reached the click path. **[After: Pr454 Failed Counts Success](https://app.greptile.com/trex/artifacts/293fa0c1-8663-459f-9a98-12b5a00f1c55)** - What the screen looked like at this point in the check. **[After: Pr454 Failed Counts Failure](https://app.greptile.com/trex/artifacts/60997d03-470a-470f-be48-710650912213)** - What the screen looked like at this point in the check. **[After: Pr454 Failed Counts Empty](https://app.greptile.com/trex/artifacts/4979ecb2-b282-4ef4-9013-99f5024e6b98)** - What the screen looked like at this point in the check. **[▶ page@b1577e3ac2036f8690dfae10f2d355d0.webm](https://app.greptile.com/trex/artifacts/b8ad8f63-abf6-4366-ae6b-f57d29efc060)** - The flow T-Rex ran, recorded end to end. **[Before: Pr454 Failed Counts Success](https://app.greptile.com/trex/artifacts/56846c8e-03bc-40ae-ab99-7b8aea18bcd8)** - What the screen looked like at this point in the check. **[Before: Pr454 Failed Counts Failure](https://app.greptile.com/trex/artifacts/9f43d5fc-5383-4dd3-9125-d44291742d2a)** - What the screen looked like at this point in the check. **[Before: Pr454 Failed Counts Empty](https://app.greptile.com/trex/artifacts/90962557-d069-410a-bd12-46842b9a2c3b)** - What the screen looked like at this point in the check. **[▶ page@db1d5220ce2e59a5e6a32fafa94e0c50.webm](https://app.greptile.com/trex/artifacts/64e81fbb-b1cd-46b3-ac38-b081bc7137b7)** - The flow T-Rex ran, recorded end to end. **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/b8ad8f63-abf6-4366-ae6b-f57d29efc060)** - Recorded the base component with populated, failing and empty endpoint configurations; the original overview has no activity counts or chart. **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/64e81fbb-b1cd-46b3-ac38-b081bc7137b7)** - Recorded each condition for approximately six seconds with the real composables; failed requests present the same zero activity as successful empty responses. **[Base overview with populated activity endpoints configured](https://app.greptile.com/trex/artifacts/56846c8e-03bc-40ae-ab99-7b8aea18bcd8)** - Captured the rendered base component with populated endpoints available; it does not display moderation or command activity. **[Base overview with failed activity endpoints configured](https://app.greptile.com/trex/artifacts/9f43d5fc-5383-4dd3-9125-d44291742d2a)** - Captured the rendered base component with failing endpoints available; it has no activity counts or chart to misrepresent those failures. **[Base overview with empty activity endpoints configured](https://app.greptile.com/trex/artifacts/90962557-d069-410a-bd12-46842b9a2c3b)** - Captured the rendered base component with empty endpoints available; its activity presentation remains absent. **[Head overview after populated successful requests](https://app.greptile.com/trex/artifacts/293fa0c1-8663-459f-9a98-12b5a00f1c55)** - Captured the head after successful requests settled; DOM assertions verified six cases, twelve commands and nonzero chart activity. **[Head overview after activity requests failed](https://app.greptile.com/trex/artifacts/60997d03-470a-470f-be48-710650912213)** - Captured the head after both requests reached error status; DOM assertions verified zero counts and seven zero-height bars without failure copy. **[Head overview after successful empty requests](https://app.greptile.com/trex/artifacts/4979ecb2-b282-4ef4-9013-99f5024e6b98)** - Captured the head after successful empty responses; its counts and chart accessibility text match the failed-request condition. **[Evidence from the check](https://app.greptile.com/trex/artifacts/8912b975-e7f7-4bb3-bbdb-498446d0f2d8)** - Executed this uniquely named test against both snapshots; it asserts the observed success, failure and empty-response presentations. **[Evidence from the check](https://app.greptile.com/trex/artifacts/560c70bd-12ce-4505-94a8-1f50ce5516b7)** - Used this configuration to run the component test and capture browser observations; unrelated agent tests were not included. **[Evidence from the check](https://app.greptile.com/trex/artifacts/c4c41f0a-8c42-44d2-b8be-2c55bac4ef3c)** - Exported the exact base component using git show and imported it into the browser test; no checkout was needed. **[Evidence from the check](https://app.greptile.com/trex/artifacts/23cc6a71-ac8b-4cc5-b7cd-d0c78b7a7146)** - Exported the exact head component using git show and imported it into the browser test; its activity failure behavior was exercised directly. **[Command output from the check](https://app.greptile.com/trex/artifacts/1ab25530-22b0-47c1-b5a8-b6709be2f0d1)** - Captured the successful base test command and observed DOM output; the original overview has no activity counts, chart or activity requests. **[Command output from the check](https://app.greptile.com/trex/artifacts/9a6296ab-fe1d-479b-a603-7d5bfc9151c0)** - Captured the successful head test command, request queries, error messages and equality assertions; failed activity is displayed exactly like empty activity. **[Command output from the check](https://app.greptile.com/trex/artifacts/70438c94-ff0c-4686-a05d-440c3264c8f2)** - Repeated the head test and captured rendered text, dimensions and visible ancestors alongside request results; the failure presentation exists in the rendered browser layout. **[Command output from the check](https://app.greptile.com/trex/artifacts/bdf87b92-0d78-4e3c-baaa-e4d828c058d4)** - Captured 19 git show and diff commands with line-numbered output; all four higher snapshots retain the activity status/error omission. **[Evidence from the check](https://app.greptile.com/trex/artifacts/747f3009-3e3d-45fe-bf44-75536b9ebbe8)** - The component test exercises both real card variants with Ctrl, Meta, and ordinary clicks using actual composables; its latest event-observation revision still needs execution. **[Evidence from the check](https://app.greptile.com/trex/artifacts/dd50729a-d872-4d2b-bb7a-bb04b6ddcd5c)** - The configuration launches Chromium through Playwright and supplies click, viewport, video, and poster capture commands; the latest commands still need a completed run. **[Evidence from the check](https://app.greptile.com/trex/artifacts/10446552-fa3f-41b7-a293-b53f0b1f41f4)** - The card was extracted with git show from the specified base and rendered without editing tracked source; this is the executed baseline component. **[Command output from the check](https://app.greptile.com/trex/artifacts/616e8d30-a318-4665-8720-c9f50d4504f9)** - The last completed base run captured unchanged original-tab guild, section, and route after Ctrl-click, then failed because no new tab was observed; this proves only the baseline state behavior. **[Command output from the check](https://app.greptile.com/trex/artifacts/5ae81b80-790f-48a6-87ca-3418dc486b4d)** - Git show captured numbered card and composable source from head and all four requested higher snapshots; the inspected paths retain the unconditional click handler. **[Command output from the check](https://app.greptile.com/trex/artifacts/a0076d94-fec5-4a45-9d22-791ac7467f71)** - The initial Chromium run failed because the real tooltip lacked its provider; the harness subsequently added the provider and reached the click path. **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/b8ad8f63-abf6-4366-ae6b-f57d29efc060)** - Recorded the base component with populated, failing and empty endpoint configurations; the original overview has no activity counts or chart. **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/64e81fbb-b1cd-46b3-ac38-b081bc7137b7)** - Recorded each condition for approximately six seconds with the real composables; failed requests present the same zero activity as successful empty responses. **[Base overview with populated activity endpoints configured](https://app.greptile.com/trex/artifacts/56846c8e-03bc-40ae-ab99-7b8aea18bcd8)** - Captured the rendered base component with populated endpoints available; it does not display moderation or command activity. **[Base overview with failed activity endpoints configured](https://app.greptile.com/trex/artifacts/9f43d5fc-5383-4dd3-9125-d44291742d2a)** - Captured the rendered base component with failing endpoints available; it has no activity counts or chart to misrepresent those failures. **[Base overview with empty activity endpoints configured](https://app.greptile.com/trex/artifacts/90962557-d069-410a-bd12-46842b9a2c3b)** - Captured the rendered base component with empty endpoints available; its activity presentation remains absent. **[Head overview after populated successful requests](https://app.greptile.com/trex/artifacts/293fa0c1-8663-459f-9a98-12b5a00f1c55)** - Captured the head after successful requests settled; DOM assertions verified six cases, twelve commands and nonzero chart activity. **[Head overview after activity requests failed](https://app.greptile.com/trex/artifacts/60997d03-470a-470f-be48-710650912213)** - Captured the head after both requests reached error status; DOM assertions verified zero counts and seven zero-height bars without failure copy. **[Head overview after successful empty requests](https://app.greptile.com/trex/artifacts/4979ecb2-b282-4ef4-9013-99f5024e6b98)** - Captured the head after successful empty responses; its counts and chart accessibility text match the failed-request condition. **[Evidence from the check](https://app.greptile.com/trex/artifacts/8912b975-e7f7-4bb3-bbdb-498446d0f2d8)** - Executed this uniquely named test against both snapshots; it asserts the observed success, failure and empty-response presentations. **[Evidence from the check](https://app.greptile.com/trex/artifacts/560c70bd-12ce-4505-94a8-1f50ce5516b7)** - Used this configuration to run the component test and capture browser observations; unrelated agent tests were not included. **[Evidence from the check](https://app.greptile.com/trex/artifacts/c4c41f0a-8c42-44d2-b8be-2c55bac4ef3c)** - Exported the exact base component using git show and imported it into the browser test; no checkout was needed. **[Evidence from the check](https://app.greptile.com/trex/artifacts/23cc6a71-ac8b-4cc5-b7cd-d0c78b7a7146)** - Exported the exact head component using git show and imported it into the browser test; its activity failure behavior was exercised directly. **[Command output from the check](https://app.greptile.com/trex/artifacts/1ab25530-22b0-47c1-b5a8-b6709be2f0d1)** - Captured the successful base test command and observed DOM output; the original overview has no activity counts, chart or activity requests. **[Command output from the check](https://app.greptile.com/trex/artifacts/9a6296ab-fe1d-479b-a603-7d5bfc9151c0)** - Captured the successful head test command, request queries, error messages and equality assertions; failed activity is displayed exactly like empty activity. **[Command output from the check](https://app.greptile.com/trex/artifacts/70438c94-ff0c-4686-a05d-440c3264c8f2)** - Repeated the head test and captured rendered text, dimensions and visible ancestors alongside request results; the failure presentation exists in the rendered browser layout. **[Command output from the check](https://app.greptile.com/trex/artifacts/bdf87b92-0d78-4e3c-baaa-e4d828c058d4)** - Captured 19 git show and diff commands with line-numbered output; all four higher snapshots retain the activity status/error omission. <a href="https://app.greptile.com/trex/runs/31698547/artifacts?artifact=747f3009-3e3d-45fe-bf44-75536b9ebbe8"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifactsDark.svg?v=4"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifacts.svg?v=4"><img alt="View artifacts" src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewArtifacts.svg?v=4"></picture></a> <h3>Summary</h3> Replaces guild-specific dashboard pages with a client-rendered `/app` page. - Server settings and logs now open inside one `/app` dashboard. - The dashboard adds a grouped sidebar, server rail, and phone navigation. - The dashboard brings server stats, module settings, and logs into sections. - New dashboard and server-setting labels appear across the supported languages. ### T-Rex validation blocked The server-card modifier-click check could not complete because its browser validation session reached its execution limit before running the current-card comparison. The completed baseline run does not establish or contradict the current-card behavior. <!-- greptile_confidence_score:4 --> <sub>Reviews (9) · Last reviewed commit: ["fix(dashboard): keep a single hover high..."](69b75cd) · [Reviewed by Greptile](https://www.greptile.com/?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=wolfstar-project/wolfstar.rocks)</sub> <!-- /greptile_comment -->
### 🔗 Linked issue Part of the stack superseding #422. Depends on #461. **Stack:** #451 foundation ← #452 header/footer ← #453 marketing ← #454 `/app` dashboard ← #455 V7 contract + ORM 8 only ← #457 auto-moderation rules ← #461 deprecate `/profile` ← this PR ### 🧭 Context `/commands` was the last public page still on the pre-redesign look: nested Nuxt UI accordions, heavy borders, and a heading that the layout's top glow painted over. This rebuilds it on the Claude Design mockup for the commands page. ### 📚 Description - **Layout.** Centred heading and subtitle, a 52px search field next to the Refresh button, and a single 900px panel holding the category cards. - **Categories.** Each card shows its name and a command count. One category is open at a time and Moderation opens first when the bot reports it. While searching, every category with a match is expanded. - **Commands.** Each command is its own collapsible row: mono name, truncated description, then usage, extended help, explained usage, possible formats, examples, reminder and pill badges for permission level, server-only and guarded commands. - **Search.** The result count is now visible under the field instead of screen-reader only. Matching still goes through Fuse on name, description and category. - **Copy.** Every string on the page moved to `marketing.commands.*`; the page was hardcoded English before. Other locales get empty placeholders, as usual. - **Components.** `CommandSubCategory` is gone. `CommandCategory` is one card, `CommandItem` is one command row, `CommandBase` renders the details and `CommandChips` the badges. ### 🧪 Verification - `test/nuxt/pages/commands.spec.ts` covers the default open category, single-open behaviour, search expansion and count, the no-match state and the retry state. - Checked in a local dev server with sample commands in dark, light and at 390px wide. - Lint, typecheck, knip, i18n check/report, the colour guardrail tests and `build:test` pass locally. ###⚠️ Deviations from the mockup - The mockup's Refresh resets the page state. Here it keeps refetching the command list from the bot, as before. - The mockup shows a note saying details are "pulled live from the bot" on commands without help. Details are already live here, so that note reads "This command takes no arguments and has no extra notes." - Surfaces use the app's own tokens (`base-100`, `base-content` tints, `--home-*`) rather than the mockup's hex values, so the cards sit on the page colour instead of one step lighter in the dark theme. - The loading skeleton, the "no commands available" retry state, the Possible Formats and Reminder blocks and the "Cannot be disabled" badge are not in the mockup. They are kept and restyled to match. > 🤖 AI disclosure: [Wolfstar Agent Kit](https://github.com/wolfstar-project/wolfstar-agent-kit) modified this description. [My AI open-source policy](https://harlanzw.com/blog/ai-in-open-source). <!-- greptile_comment --> <!-- greptile_summary --> <h2><a href="https://app.greptile.com/api/retrigger?id=78748592"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/RetriggerDark.svg?v=2"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2"><img alt="Retrigger" src="https://greptile-static-assets.s3.amazonaws.com/badges/Retrigger.svg?v=2" align="right"></picture></a>Confidence Score: 4/5</h2> <!-- greptile-risk --> The remaining concerns are explicit repository design and copy requirements; both must be satisfied before merging. <h3>Summary</h3> This PR rebuilds `/commands` with category cards, expandable command rows, visible search counts, and translated labels. - Command categories open one at a time and expand to show search matches. - Each command opens to show its help and badges. - The commands page gets translation keys for its labels and help text. - Czech plural forms and English fallback text use the right rules. The design and copy requirements must be satisfied before merging. <!-- greptile_confidence_score:4 --> <sub>Reviews (15) · Last reviewed commit: ["fix(ui): derive command details from the..."](a98bf3a) · [Reviewed by Greptile](https://www.greptile.com/?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=wolfstar-project/wolfstar.rocks)</sub> <!-- /greptile_comment -->

🔗 Linked issue
Part of the stack superseding #422. Depends on #457.
Stack: #451 foundation ← #452 header/footer ← #453 marketing ← #454
/appdashboard ← #455 V7 contract + ORM 8 only ← #457 auto-moderation rules ← #461 deprecate/profile🧭 Context
Since #454 the dashboard lists the servers you manage itself, so
/profilewas a second server list with a different layout. It was also the only place to change theme, language and reduced motion, which is why it could not simply be deleted: guests use those controls too, and/appis sign-in only.📚 Description
/profileand/accountnow answer with a 301 to/app:The preferences moved into a dialog opened from the header's Settings button (guests) and from the user menus (signed in), so nobody needs a page for them. Sign-in lands on
/app, which shows the server picker when no server is selected./appshows every server you can manage, sorted with the ones WolfStar is in first. If someone manages enough servers to need search, it should be added to the picker rather than kept on a second page./profileshowed them when the toggle was off./profilewas indexable and had an OG image. It is nowrobots: falseand out of the sitemap.I used 301 because the page is not coming back; say so if you would rather keep it temporary while the stack is in review.
The settings-dialog copy requirements must be satisfied before merging; clipped activity descriptions, stale dates, and incorrect context-menu labels are non-blocking usability concerns.
T-Rex evidence
▶ Recording of the check
▶ Recording of the check
Original activity table at maximum horizontal scroll
PR461 activity table at maximum horizontal scroll
Command output from the check
Evidence from the check
Evidence from the check
Evidence from the check
Evidence from the check
▶ Recording of the check
▶ Recording of the check
Original activity table at maximum horizontal scroll
PR461 activity table at maximum horizontal scroll
Command output from the check
Evidence from the check
Evidence from the check
Evidence from the check
Evidence from the check
Summary
The PR redirects
/profileand/accountto/appand moves browser preferences into a shared settings dialog.Reviews (14) · Last reviewed commit: "fix(dashboard): narrow the pager item be..." · Reviewed by Greptile