Repository navigation
refactor: v3 - #422
refactor: v3#422RedStar071 wants to merge 34 commits into
Conversation
Brings AppHeader/AppFooter in line with the Navbar and Footer artboards of the WolfStar Web Redesign canvas: - Header brand lockup is now the app icon plus its name for both WolfStar and Staryl, replacing the oversized WolfStar wordmark. - The desktop nav pill gains a subtle border, a 36px item height and an open state, matching the surface-rail pill in the canvas. - Features entries get leading icons, Applications entries get short taglines (new `nav.wolfstar_description` / `nav.staryl_description`). - The header bar is translucent with a backdrop blur so content scrolls under it. - Sign-in switches to the soft primary button of the canvas. - Footer: the Netlify badge leads with a deploy status dot, and the build info in the bottom bar renders as a bordered mono status pill. Tests covering the previous logo mark and footer build-info classes are updated, plus new useHeader coverage for the icons and descriptions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
…esign canvas Home and Staryl, from the Main and Staryl artboards: - Both product-proof sections become a three-card grid, each card carrying a brand-tinted icon badge, its step number, and (on WolfStar) the step's CTA, replacing the two-column text list. - Both hero briefs render as elevated cards with brand-tinted terms instead of a pair of hairline rules. - Both closing CTAs become a centred panel with a brand rule along its top edge and a faded app watermark, replacing the inverted neutral block. - Other Apps moves to a heading-plus-card layout, with the app logo, its short description, and invite/explore actions in one card. - The dashboard section now precedes the commands demo on the home page, as the artboard orders them. Dashboard, from the Dashboard artboard: the layout gains a server rail listing the guilds the viewer manages that WolfStar is in, so switching servers no longer means going back to the profile page. The rail's selection rule lives in `selectDashboardRailGuilds()` and is unit-tested; the component itself is covered by a Nuxt spec plus an axe audit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
Restores AppLogoMark for WolfStar (and the plain app name for Staryl) in place of the avatar image, and restores the audit that keeps the resized svg hidden from assistive tech. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
◈ PR Lens
Architecture 17 components touched across 6 lanes. Play the interactive walkthrough Inside the changed components — 2 viewsComponent view — Dashboard Client & State Single-page dashboard architecture showing state coordination, server rail, modules configuration, and local storage persistence Component view — Prisma ORM 8 & Database Layer Dual Prisma ORM architecture with typed ORM 8 postgres client for queries and ORM 7 client for serializable audit transactions Data flow
Follow each request, response and payload The other flows — 1 sequence
View
Tip Run 🪧 More tips
Thanks for using PR Lens! It's built by Coldtea, free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
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
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ef87d34f5c
ℹ️ 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".
Replaces the V6 flat `guilds` model with the bot's V7 contract (wolfstar-project/wolfstar -> projects/database/src/prisma8/contract.prisma): Guild plus GuildAutoModeration and its seven filter tables, GuildLogs, GuildModeration, GuildRoles, ModerationAction, StickyRole, User, and the audit/command-log tables in their V7 shape (BigInt snowflakes, snake_case columns, native enums). Adds the dashboard-owned Modules table: one boolean per feature, one row per guild, in the spirit of Dyno's module switches. Subsystems the bot ships today (automod, moderation, logs, commands, roles) default on; planned ones (welcome, leveling, starboard, music, suggestions) default off. The migration is written to no-op against a database the bot has already migrated, and archives the V6 tables as `<name>_v6` instead of dropping them, so a later backfill can still read the old settings, cases and audit chain. The full migration history replays cleanly on an empty Postgres, and the new migration is idempotent on re-run. The app code still speaks the V6 model, so it does not compile against this schema yet; porting it is the next step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c06bf3466b
ℹ️ 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".
| DECLARE | ||
| legacy TEXT; | ||
| BEGIN | ||
| FOREACH legacy IN ARRAY ARRAY['guilds', 'moderation', 'schedule', 'migrations', 'audit_event', 'audit_chain_head', 'command_log'] |
There was a problem hiding this comment.
Backfill legacy audit data before archiving it
When this migration runs against the existing V6 schema, this loop renames audit_event and audit_chain_head after creating empty V7 replacements, without copying the rows or seeding the new head. The activity-log route will therefore lose all historical entries, and the next event written by postgres-drain.ts starts from a null head rather than the prior hash, breaking continuity of the tamper-evident audit chain; perform the data-specific backfill and carry forward the chain head before switching tables.
AGENTS.md reference: AGENTS.md:L174-L174
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Same answer as the V6 settings thread, plus a guard added in 3cc4f16.
The copy itself is deliberately out of scope: the bot owns the shared database and migrates it to V7 in projects/database, so in the deployment this targets AuditEvent and AuditChainHead are already populated and every statement here no-ops. Carrying the chain across is not a rename either — V7 changes the actor/target id types, so the envelope payloads have to be re-canonicalized before they re-hash, and picking that mapping decides whether the old chain stays verifiable. That is a call for @RedStar071 to place (here or in the bot's project), not something to infer in a review round.
The half that was wrong is fixed: the rename no longer fires against a still-V6 database. A populated audit_event/audit_chain_head is only archived once "Guild" holds rows, i.e. after the bot's V7 migration, so this migration running first leaves the tables — and the existing chain head — untouched under their original names rather than leaving the next write to start from a null head. Empty tables are still archived, so fresh databases behave as before.
Generated by Claude Code
…ore permission nodes - Modules keeps only the toggles that map to subsystems the bot ships: automod, moderation, logs, commands, roles. The planned ones (welcome, leveling, starboard, music, suggestions) are gone. - GuildAutoModeration, GuildLogs, GuildModeration, GuildRoles and the new GuildPermissions now hang off Modules instead of Guild, so a module row is the parent record for its subsystem's configuration and deleting it cascades the settings away. - GuildPermissions restores the V6 `permissions.users` / `permissions.roles` nodes that the V7 contract dropped, as two typed JSON columns, because `canManage()` grants dashboard access through them. Verified on an ephemeral Postgres: the migration history replays clean and stays idempotent, settings rows are rejected without a Modules parent, and deleting a Modules row takes them with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1be15ba78f
ℹ️ 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".
| /// The type of actor that performed the action. | ||
| actorType String @map("actor_type") | ||
| /// The ID of the actor that performed the action. | ||
| actorId BigInt @map("actor_id") |
There was a problem hiding this comment.
Convert V7 audit IDs before verifying the chain
When AuditEvent contains any V7 row, scripts/audit-verify.ts passes Prisma's bigint values directly to verifyPersistedAuditChain(), whose persisted-row contract expects decimal strings; the reconstructed envelope then reaches canonicalize(), which explicitly throws on BigInt. Consequently, pnpm audit:verify terminates with a fatal error instead of checking the tamper-evident chain. Normalize actorId, targetId, and tenantId to strings before reconstruction.
AGENTS.md reference: AGENTS.md:L174-L174
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed. scripts/audit-verify.ts contains no string normalisation at all, so once AuditEvent.actorId/targetId/tenantId are BigInt columns the rows reach verifyPersistedAuditChain() as bigints and canonicalize() throws before a single hash is checked.
Same root cause as the actor-id finding on this file: the V7 schema is in place, the code that reads it has not been ported, and that is what the red Typecheck check is reporting. Not fixed in this PR on purpose — the port is the database owner's to make.
Worth noting the two have to be solved together and in the same direction: whatever conversion the drain writes with, the verifier has to undo identically, or the replayed envelope will not reproduce the stored hash and the chain will read as tampered.
Generated by Claude Code
…bled commands Brings back four pieces of V6 configuration the V7 contract dropped but the dashboard still manages: - GuildAutoModerationNoMentionSpam sits beside the other automod subsystems and carries their shape (enabled, soft/hard action, thresholds, ignore lists) plus its own alerts, mentions allowance and decay window. - GuildRoles gains the roles the dashboard configures beyond the initial ones: admin, moderator, muted, and the five restricted roles. - Guild carries the guild locale again. - GuildCommands, under Modules, holds the commands a guild has switched off: server-wide, per channel, and per command-and-channel. Verified on an ephemeral Postgres: the migration history replays clean, stays idempotent, and now produces exactly the schema Prisma generates — 194 columns and 41 constraints identical between the two. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
GuildRoles carries the last three role settings the dashboard's Roles page manages: the roles members may claim for themselves, the groups a member may only hold one role of, and whether claiming a public role drops the initial one. Existing databases pick them up through guarded ADD COLUMN statements, and the migration history still produces exactly the schema Prisma generates (197 columns, 41 constraints identical). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc7f78452b
ℹ️ 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".
ModerationAction carries the reason again, so the dashboard's moderation log can show and search it. The V6 `image_url` column does not come back: the log shows `reference_id` — the case an entry refers to — in its place, which V7 already carries. Existing databases pick the column up through a guarded ADD COLUMN, and the migration history still produces exactly the schema Prisma generates (198 columns, 41 constraints identical). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
The floating save-changes bar already had a `save-changes-bar` view transition name and matching CSS, but a Vue `<Transition>` was animating it instead. Its visibility is now committed inside a view transition, so the browser's before/after snapshots drive it and the enter/leave classes are gone. Adds `startViewTransition()` in `app/utils/view-transition.ts` for the guard the dashboard and the guild cards were each hand-rolling: it applies the update directly on the server, without browser support and under reduced motion, and either skips a running transition or rides along with it. Unit-tested across all five paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 72e4446d00
ℹ️ 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".
| language String @default("en-US") @db.VarChar | ||
| disableNaturalPrefix Boolean @default(false) @map("disable-natural-prefix") | ||
| disabledCommands String[] @default([]) @map("disabled-commands") @db.VarChar(32) | ||
| id BigInt @id |
There was a problem hiding this comment.
Adapt settings persistence before replacing Guild
After this model becomes the generated Guild type, readSettings() still treats prisma.guild.findUnique() as the old flat settings record and serializeSettings() only converts the former duration fields. For every existing guild, the returned id is now a bigint, so the settings GET throws during JSON.stringify; for a missing guild, the create path instead submits dozens of removed V6 properties. Consequently no guild dashboard can load or initialize its settings until these helpers assemble the new related V7 tables into the existing API DTO.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed. Guild is now id BigInt + language + the modules/cases/stickyRoles relations — every flat settings column is gone — so readSettings() is reading a record that no longer has the shape it assumes, and the create path would submit dozens of removed V6 properties. The bigint id breaking JSON.stringify on the GET is the same class of problem as the command-log DTO fixed earlier in this PR.
Not fixed here, deliberately: this is the V6→V7 code port, which is what the red Typecheck check is reporting (~402 errors) and which belongs to the bot, the owner of the shared database. Reassembling guild settings out of the related V7 tables is a substantial rewrite of the settings read/write path plus its audit field metadata, and it should not ride along with the ORM 8 adoption.
Generated by Claude Code
| /// The type of actor that performed the action. | ||
| actorType String @map("actor_type") | ||
| /// The ID of the actor that performed the action. | ||
| actorId BigInt @map("actor_id") |
There was a problem hiding this comment.
Preserve string actor IDs at the audit persistence boundary
Every audit producer and AuditEnvelope supplies actor.id as a string, and createPostgresAuditDrain() passes that value unchanged to tx.auditEvent.create(). Changing this field to BigInt therefore makes Prisma reject audit writes (and also cannot represent non-snowflake actor IDs such as generated auth IDs), so security-relevant actions stop reaching the hash chain; retain a string column or explicitly define and implement a lossless conversion contract at the drain and read boundaries.
AGENTS.md reference: AGENTS.md:L174-L174
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed, and it is not fixed in this PR.
AuditEvent.actorId is now BigInt (with targetId/tenantId as BigInt?), while createPostgresAuditDrain() still takes audit.actor.id straight off the envelope — a string — and hands it to tx.auditEvent.create(). Every audit write would be rejected, and you are right that a snowflake-shaped column also cannot represent the generated better-auth actor ids that user.* events carry.
This is one of the ~402 errors the red Typecheck check is reporting: the V7 schema landed, the code that reads and writes it has not been ported. That port is deliberately staged and belongs to the database's owner (the bot), not to this PR — porting it here would mean shipping an untested rewrite of the audit persistence boundary alongside the ORM 8 adoption.
My proposal for when it is picked up: keep the envelope's string as the contract and convert at the drain and read boundaries only, rejecting non-numeric actor ids explicitly rather than letting BigInt() throw. That keeps canonicalize()'s no-BigInt rule intact and keeps the hash chain reproducible from the envelope alone.
Generated by Claude Code
- Regenerate the locale schemas so the new `nav.*` and `dashboard.server_rail_*` keys validate; the committed schemas disallow additional properties, so the i18n check rejected the locale files without this. - Re-point `GuildAutoModeration`, `GuildCommands`, `GuildLogs`, `GuildModeration`, `GuildPermissions` and `GuildRoles` at `Modules` even when a constraint of that name already exists: the bot's V7 parents them on `Guild`, and a name-only guard silently kept that parent, leaving settings rows with no module and no cascade. Each constraint is now inspected, dropped when it references anything else, and the parent module rows are backfilled first. - Render the dashboard server rail from first paint, with placeholders while the guild list loads, so it no longer appears after the client fetch and shifts the sidebar and page content. - Drop the comments that only labelled markup or restated the CSS under them. Verified on an ephemeral Postgres: the history replays clean and idempotent, migration and schema still match exactly (198 columns, 41 constraints), and against a database already migrated by the bot the four settings tables end up parented on `Modules`, backfilled and enforced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
|
Status after 888fc5b, and what is still red. Pushed in 888fc5b (review findings, all verified locally):
Five checks are failing on something that is not this PR.
One more thing worth a decision rather than a patch: The branch is also behind Generated by Claude Code |
`useRouteQuery` comes from `@vueuse/router`, which this project does not depend on, so the identifier was never auto-imported and both pages threw `useRouteQuery is not defined` on every server render. Verified against a dev server: `/oauth/guild` and `/oauth/callback` each returned 500, and `/oauth/login` (which does not use it) returned 200. Both call sites only read the query, so `useRoute().query` covers them with no new dependency. `callback.vue` already held a `route` const and read `route.query.*` on the adjacent lines, so this matches what was around it; `guild.vue` gains one. This also unblocks CI. `/oauth/guild` had started being reached by the prerender crawler, and the 500 is what "Exiting due to prerender errors" was hiding, taking Browser tests, Performance and both Accessibility audits down with it. The bug itself predates this branch -- it arrived in 9fdb68f (#351) and is live on main, where it breaks the post-sign-in landing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
The card's manage links called `useActiveGuild().selectGuild()` directly, which switches the active guild unconditionally. With settings staged on another server that silently discarded the edits, because the check for pending changes lives in `useDashboardNavigation().goToGuild()` -- the same boundary `layouts/dashboard.vue` already goes through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
`guildSettingsChanges.spec.ts` still mocked `useRouteParams`, but the composable now reads the active guild from `useActiveGuild()`. The mock intercepted nothing, so `activeGuildId` stayed null and every write no-opped: four tests failed outright and the two asserting `toBeUndefined` passed for the wrong reason. It now drives `useActiveGuild()` directly, with snowflake-shaped ids since `selectGuild()` rejects anything else, and adds a case covering the no-active-guild guard that the broken mock had been silently exercising all along. `Modules.spec.ts` reached into `setupState` through `as any`, against the CLAUDE.md rule. `setupState` is a real Vue internal that the public `ComponentInternalInstance` type omits, so name it in an intersection instead of erasing the type entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
|
Pushed three commits that clear the build break, the failing spec and both review findings. The prerender failure was a 500 on
Both call sites only read the query, so This one is not this branch's bug. It arrived in 9fdb68f (#351) on 2026-08-04 and is live on The other two commits:
Verified locally: Typecheck and the data migration are still open and still belong with the bot. Generated by Claude Code |
|
Decision: the V7 settings port is being split into a follow-up PR, so Typecheck stays red here deliberately. Everything else on this PR is green: 27 check runs pass, including Why it isn't being fixed in this PR. V7 decomposed the flat Scope of the follow-up, by error concentration:
The V6→V7 data backfill and the audit hash-chain carry-over stay out of that follow-up too — they belong to the bot. One correction to my previous comment, for anyone reproducing locally: if you see ~60 Generated by Claude Code |
Resolves the merge conflict that made #422 un-mergeable. `main` had moved on by six commits — the Components v2 link cards (#429), the Pullfrog workflow (#430), an AGENTS.md refresh (#437), the linter updates (#434) and the actions/checkout and chromaui/action bumps (#435, #436). Only `pnpm-lock.yaml` conflicted, because both sides changed dependencies: this branch added `@prisma/orm-postgres` and moved the Prisma 7 scripts behind `prisma7.config.ts`, while main bumped the linters. Regenerated it with `pnpm install` rather than resolving by hand, so the result carries both sides; `package.json` merged cleanly on its own. Verified: `vp run build:test` completes with no errors, the full suite passes (1615 tests, including main's new discord-component-embed specs), and lint is warnings-only. Typecheck is still red on the V6→V7 `GuildData` port that is deliberately split into a follow-up, but the count fell from 586 to 415 and none of the remaining errors are in the files this merge brought in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
Resolves the conflicts main's Nuxt 4.6 upgrade (#447) introduced: - package.json: take main's `@nuxt/test-utils` 4.3.3 and keep this branch's `@prisma/prisma7` dev dependency. Main's replacement of the `h3-next` alias with plain `h3` carries over unchanged. - pnpm-lock.yaml: regenerated with pnpm rather than hand-merged, so the Nuxt 4.6 / @netlify/nuxt 1.0 bumps and this branch's Prisma 8 contract packages both resolve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
Addresses four review findings on the ORM 7 to ORM 8 migration. A settings PATCH outlives the guild it was issued for: the admin can confirm a switch while it is in flight. `setGuildSettings()` and `setGuildSettingsChanges()` resolve their target from `activeGuildId` at call time, so a late response wrote guild A's settings under guild B and cleared B's staged edits. Both now take an optional target guild, and `submitChanges()` captures the id before awaiting. `AuditEvent` rows reach `verifyPersistedAuditChain()` straight from the ORM 8 façade, which types the snowflake columns as `BigInt` and `timestamp(3)` as `TimestampString`. `envelopeFromPersistedRow()` calls `.toISOString()` on the timestamp, so `pnpm audit:verify` threw on any non-empty chain instead of checking hashes, and the envelope rejects `BigInt` outright. The script now maps rows into the structural shape the drain hashed. The same codec change made the log routes return PostgreSQL's zoneless timestamp text where ORM 7 returned a `Date`, so the dashboard tables' `new Date()` read every log time in the browser's own zone. The three routes convert through a new `timestampStringToUtcIso()` helper, which assumes UTC the way the ORM 7 client did. Also drops the two hero-section comments that only restated the styling below them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016oumZcNbZjhJGPivv4BnfW
|
Superseded by the
Branch |
### 🔗 Linked issue Part of the stack superseding #422. Depends on #451. **Stack:** #451 foundation ← #452 header/footer ← #453 marketing ← #454 `/app` dashboard ← #455 V7 schema + Prisma ORM 8 (draft) ### 🧭 Context The header and footer had drifted from the WolfStar Web Redesign canvas. ### 📚 Description The brand lockup is unchanged apart from the Staryl wordmark's size (WolfStar keeps its logo mark, Staryl stays text only), the header is one solid surface matching the page, and the desktop nav pill gets a border, a 36px row and an open state. Features entries carry an icon and a description, Applications entries their avatar and a tagline, rendered through `UNavigationMenu` content slots. Those rows are plain links, so the panel closes itself on select or it would stay over an in-page anchor target. Desktop and mobile menus are built from the same groups in `useHeader()`. The footer badge leads with a deploy status dot and the build info is a mono status pill. Two new `nav.*_description` keys, empty in every non-English locale. > 🤖 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=78359963"><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: 5/5</h2> <!-- greptile-impact-verdict --> No merge-blocking issues are identified in the reviewed changes. <h3>Summary</h3> The PR updates the shared header and footer to match the redesign. - The header matches the redesign while keeping both app names distinct. - Desktop menus show what each feature and application offers. - The footer shows deployment and build details as compact status displays. No actionable issues remain in this review. Tests and browser checks were not run. <!-- greptile_confidence_score:5 --> <sub>Reviews (6) · Last reviewed commit: ["fix(ui): keep focus on the header menu a..."](342d868) · [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 -->
…showcases (#453) ### 🔗 Linked issue Part of the stack superseding #422. Depends on #452. **Stack:** #451 foundation ← #452 header/footer ← #453 marketing ← #454 `/app` dashboard ← #455 V7 schema + Prisma ORM 8 (draft) ### 🧭 Context Marketing sections on `/` and `/staryl` follow the canvas. On top of the visual port from #422 this layer defers hydration of the heavy demos, which the original PR did not do. ### 📚 Description Hero briefs become cards, product proof moves to a three-card grid with an icon per step, CTAs become a centred bordered panel with a faded brand watermark. `OtherApps` renders siblings as `SurfaceCard`s and the WolfStar home shows the dashboard section before the commands demo. The three showcases below the fold (`WolfstarModerationShowcase`, `WolfstarCommandsShowcase`, `StarylCommandsShowcase`) are 600 to 1300 lines each and were already `Lazy` chunks. They now use `hydrate-on-visible`, so the SSR markup is unchanged but their JavaScript stops competing with the hero until they scroll into view. Not measured yet; happy to run Lighthouse before/after on the preview if wanted. > 🤖 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=78359978"><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: 5/5</h2> <!-- greptile-impact-verdict --> No outstanding findings block merging this PR. The approved card layout remains a maintainer-owned design-guide decision. <h3>Summary</h3> Updates the marketing pages with card-based hero briefs, proof sections, and centered closing panels. - Product proof appears as three cards with an icon for each step. - The closing calls to action sit in centered panels with faint brand marks. - Showcase demos wait to hydrate until they are near the viewport. - Hero briefs appear as filled cards with primary-color labels. - Other apps appear as cards with their details and actions. <!-- greptile_confidence_score:5 --> <sub>Reviews (6) · Last reviewed commit: ["fix(ui): preload lazy showcases early an..."](1823c81) · [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 -->
…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 -->
…ly (#455) ### 🔗 Linked issue Part of the stack superseding #422. Depends on #454. Follows the bot's V7 branch, wolfstar-project/wolfstar#208. **Stack:** #451 foundation ← #452 header/footer ← #453 marketing ← #454 `/app` dashboard ← #455 V7 contract + ORM 8 only ← #457 auto-moderation rules ### 🧭 Context The bot's V7 branch runs on Prisma ORM 8 only and reshaped the database: settings live in normalized tables under `Modules`, and auto-moderation is one `GuildAutoModerationRule` row per rule instead of per-filter columns. The first commit on this branch followed an earlier snapshot of that contract, kept Prisma 7 next to ORM 8, and left typecheck red because nobody had decided how one settings view maps onto the new tables. The bot has decided since, so this PR copies its answer. ### 📚 Description **Prisma 7 is gone.** `schema.prisma`, `prisma7.config.ts`, `@prisma/client`, the pg adapter, the generator and every `prisma:migrate:*` script are removed. `prisma:generate` now runs `prisma contract emit`, so Netlify and CI call the same task as before. **The contract comes from the bot's repository.** `vendor/wolfstar` is a git submodule of wolfstar-project/wolfstar tracking `V7`, and `prisma.config.ts` reads `projects/database/src/contract.prisma` from it. The bot changes the contract and migrates first; this app follows by bumping the submodule, which Renovate can open as a PR. Versions match the bot's (`@prisma/orm-postgres` rc.13, `prisma` rc.19, the typed-JSON extension with its patch). A unit test compares the emitted storage hash with the one the pinned bot commit records for its migrations: ``` "storageHash":"c43f2589cd8e65306a24f8c685dddf640e494f3301c6087bfe5da06da7e4412c" ``` The artefacts are emitted into the gitignored `server/database/generated/prisma/` on `postinstall`, which also initializes the submodule when a checkout left it empty, so no workflow needs `submodules: true`. **Settings model.** `GuildData` is the bot's flat object; `columns.ts` maps each key to its table and column and `storage.ts` reads and writes the tables in one transaction. The settings UI moves to those keys: Home, Modules, Auto-moderation, Moderation, Channels, Roles, Commands, Reports. V7 has no prefix, no `selfmod*` filters and no `events*` / `messagesModeration*` settings, so the seven filter pages and the Events page are removed here; #457 brings auto-moderation back as rules. **Audit drain on ORM 8.** The façade exposes no isolation level, so appends are serialized with the advisory lock the bot's `AuditLogManager` takes. Two things I found on the way and fixed here: - Channels always opened empty, whatever was saved. It now shows the saved channels. - rc.13 rejects `url: ""` when the client is constructed, which broke prerendering at build time where `DATABASE_URL` is unset. The client gets `undefined` instead and connects lazily. Settings PATCH bodies used to be written as sent. They now go through a validator that refuses unknown keys and wrong value types with a 400. ###⚠️ Things a reviewer should decide - **Migrations belong to the bot now**, and its ORM 8 migrations are visible under `vendor/wolfstar/projects/database/migrations/`. I kept `server/database/migrations/` because `20260920000000_v7_contract_and_modules` is still the only DDL for the tables the contract marks `@@control(external)`; nothing here applies it. It should move to the bot's repo, after which the directory can go. - **Only the contract is shared.** The bot's `columns.ts` / `storage.ts` import its own build output, so they are ported here by hand and have to follow a submodule bump manually; typecheck catches a column that disappeared. Publishing `wolfstar-database` as a package would remove the ports. - **The bot caches settings per process** and only drops the cache on its own writes. A dashboard save reaches the database at once but the bot acts on it only after its cache is invalidated. Same as before this PR, more visible now that both sides share one model. - **The two services hash audit rows differently** (`shared/audit/envelope.ts` here, `AuditLogEnvelope.ts` in the bot), so `pnpm audit:verify` will flag rows the bot wrote. Not introduced here, not solved here. - Locale files: a translation is kept only where its English source did not change, everything else is an empty placeholder. > 🤖 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=78360005"><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 unresolved settings-column mismatch blocks merging safely for databases built from the supplied migrations; the duplicate-user validation and older-history formatting concerns are non-blocking. <h3>Summary</h3> This PR switches the dashboard to the bot’s pinned V7 contract and Prisma ORM 8. - The dashboard builds its database client from the bot’s V7 contract. - Dashboard settings map to the bot’s normalized tables. - The dashboard’s settings pages follow the bot’s V7 sections. - Dashboard audit writes share the bot’s lock for the audit chain. - Log pages and API queries use the ORM 8 database contract. - Settings controls use the V7 keys and show saved values. - Locale text and schemas match the V7 dashboard settings. <!-- greptile_confidence_score:4 --> <sub>Reviews (15) · Last reviewed commit: ["feat(dashboard): name the sections Auto ..."](4c434a3) · [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 #455. **Stack:** #451 foundation ← #452 header/footer ← #453 marketing ← #454 `/app` dashboard ← #455 V7 contract + ORM 8 only ← #457 auto-moderation rules ### 🧭 Context V7 stores auto-moderation as one row per rule (type, thresholds, actions, JSON options). #455 removed the seven V6 filter pages because their columns no longer exist; without this layer the dashboard cannot configure auto-moderation at all. ### 📚 Description A rule list and editor in the Auto-moderation section, backed by `GET/POST /api/guilds/:guild/automod/rules` and `PATCH/DELETE …/rules/:rule`. Rules are rows rather than settings keys, so they skip the staged-changes bar and save when confirmed. Request bodies go through the bot's own validator, ported with its limits and its `confusables` word normalization, so a rule written here is one the bot's API would accept. The editor covers all 21 rule types from the type's default options, plus thresholds, soft actions, punishment, escalation steps and exemptions. ###⚠️ Open questions - **The bot will not see a rule until its cache drops.** It caches rules per process and invalidates on its own shard messages. The bot already exposes these same routes behind its own auth; proxying to them would fix this, but the dashboard cannot authenticate against the bot's API today. I wrote to the database directly, as the settings routes do. If that is not acceptable, hold this PR. - I have not run this against a live V7 database. The service, validator and components are covered with mocked storage only. - Name uniqueness across processes relies on the index `GuildAutoModerationRule_guild_id_name_key`, whose name I took from the bot's code. The contract does not declare it, so I could not confirm it exists. > 🤖 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=78384596"><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: 3/5</h2> <!-- greptile-risk --> Do not merge until the two outstanding issues are fixed: a successful API save can leave a Timeout that the bot skips, and saving an older editor can silently undo another admin’s saved configuration. <h3>Summary</h3> Adds dashboard controls to create, edit, enable, and delete auto-moderation rules. - Dashboard admins can create and manage auto-moderation rules. - Guild admins can read and change auto-moderation rules through the API. <!-- greptile_confidence_score:3 --> <sub>Reviews (14) · Last reviewed commit: ["fix(dashboard): lock rule writes across ..."](67278d5) · [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 `/app` dashboard ← #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 `/profile` was 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 `/app` is sign-in only. ### 📚 Description `/profile` and `/account` now answer with a 301 to `/app`: ``` /profile -> 301 http://localhost:5678/app /account -> 301 http://localhost:5678/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. ###⚠️ What this removes - **Search, sort and the "manageable only" toggle on the server list.** The picker in `/app` shows 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. - **Servers you cannot manage** are no longer listed anywhere. `/profile` showed them when the toggle was off. - **The profile header**: avatar, username and the copy-user-id button. - `/profile` was indexable and had an OG image. It is now `robots: false` and 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. > 🤖 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=78706812"><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 settings-dialog copy requirements must be satisfied before merging; clipped activity descriptions, stale dates, and incorrect context-menu labels are non-blocking usability concerns. <h2>T-Rex evidence</h2> **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/2e06d5fd-c8bc-44c9-a9bf-ba955fb11453)** - 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. **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/43922602-d54d-4492-a1a9-9e6c011d9f2e)** - 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](https://app.greptile.com/trex/artifacts/72ca460a-a115-4bdf-9c7a-7041eda5188e)** - 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](https://app.greptile.com/trex/artifacts/5efec520-4094-4d09-b7f3-1ba142c372a2)** - The PR461 table is captured at the same width and scroll position, showing that horizontal scrolling does not recover clipped action text. **[Command output from the check](https://app.greptile.com/trex/artifacts/6087bbe7-5ff0-4aaa-b7bc-f155a36e9878)** - The successful command captured measured cell widths, computed styles, scroll limits, hover results, and checkout checks, confirming the same clipping in both PR heads. **[Evidence from the check](https://app.greptile.com/trex/artifacts/3067230c-b098-4609-9918-8e7d0bac829f)** - The script loads component source directly from local commits and captures runtime measurements and recordings; its final declaration-generation option adjustment was not rerun. **[Evidence from the check](https://app.greptile.com/trex/artifacts/a7b38dd3-b67f-48d5-a6fd-61af2520ae41)** - The harness mounts the selected commit’s activity component with real Nuxt UI components and application CSS, exercising the desktop rendering path. **[Evidence from the check](https://app.greptile.com/trex/artifacts/17ed5882-d56e-49c9-9e6b-8800cb2ba214)** - The fixtures supply long resolved role and channel names plus a short control entry, reproducing clipping without database or authentication access. **[Evidence from the check](https://app.greptile.com/trex/artifacts/1768f28b-a235-43ec-99ed-cf3800440a60)** - The shell command launches the Chromium validation with the installed browser cache and records command, working directory, and exit code in the execution log. **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/2e06d5fd-c8bc-44c9-a9bf-ba955fb11453)** - 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. **[▶ Recording of the check](https://app.greptile.com/trex/artifacts/43922602-d54d-4492-a1a9-9e6c011d9f2e)** - 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](https://app.greptile.com/trex/artifacts/72ca460a-a115-4bdf-9c7a-7041eda5188e)** - 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](https://app.greptile.com/trex/artifacts/5efec520-4094-4d09-b7f3-1ba142c372a2)** - The PR461 table is captured at the same width and scroll position, showing that horizontal scrolling does not recover clipped action text. **[Command output from the check](https://app.greptile.com/trex/artifacts/6087bbe7-5ff0-4aaa-b7bc-f155a36e9878)** - The successful command captured measured cell widths, computed styles, scroll limits, hover results, and checkout checks, confirming the same clipping in both PR heads. **[Evidence from the check](https://app.greptile.com/trex/artifacts/3067230c-b098-4609-9918-8e7d0bac829f)** - The script loads component source directly from local commits and captures runtime measurements and recordings; its final declaration-generation option adjustment was not rerun. **[Evidence from the check](https://app.greptile.com/trex/artifacts/a7b38dd3-b67f-48d5-a6fd-61af2520ae41)** - The harness mounts the selected commit’s activity component with real Nuxt UI components and application CSS, exercising the desktop rendering path. **[Evidence from the check](https://app.greptile.com/trex/artifacts/17ed5882-d56e-49c9-9e6b-8800cb2ba214)** - The fixtures supply long resolved role and channel names plus a short control entry, reproducing clipping without database or authentication access. **[Evidence from the check](https://app.greptile.com/trex/artifacts/1768f28b-a235-43ec-99ed-cf3800440a60)** - The shell command launches the Chromium validation with the installed browser cache and records command, working directory, and exit code in the execution log. <a href="https://app.greptile.com/trex/runs/31709122/artifacts?artifact=2e06d5fd-c8bc-44c9-a9bf-ba955fb11453"><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> The PR redirects `/profile` and `/account` to `/app` and moves browser preferences into a shared settings dialog. - Old profile links lead to the dashboard, while settings open in a dialog. - The dashboard picker lists manageable servers and reports load errors. - Dashboard logs use a shared toolbar, responsive rows, and tab counts. - Guild settings and selection controls are updated for the dashboard. <!-- greptile_confidence_score:4 --> <sub>Reviews (14) · Last reviewed commit: ["fix(dashboard): narrow the pager item be..."](aaaf549) · [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
No tracking issue — this started as the redesign-canvas alignment work and grew to carry the database move onto the bot's V7 contract.
🧭 Context
Two threads landed together because they could not be separated cleanly.
The first is visual: the marketing pages, the app chrome and the dashboard were drifting from the WolfStar Web Redesign canvas, so navbar, footer, hero, CTA and proof sections were brought back in line with it.
The second is structural: the bot is moving its database from the V6 schema to the V7 contract, where
Modulesbecomes the parent of the per-feature settings tables. The dashboard reads that same shared database, soschema.prismahad to follow, and following it is what pulled in Prisma ORM 8 — the V7 contract is what the bot publishes, and the ORM 8 client is how this app consumes it without owning the migrations.📚 Description
Marketing and app chrome. Header brand lockup is the app icon plus its name for both WolfStar and Staryl. The desktop nav pill gains a border, a 36px item height and an open state; Features entries get leading icons and Applications entries get taglines. The header is translucent so content scrolls under it. The footer's Netlify badge leads with a deploy status dot and the build info renders as a bordered mono status pill. Hero, CTA and product-proof sections on both marketing pages follow the canvas.
Dashboard shell. The dashboard is now a single
/apppage that keeps the active guild in state, with a server rail (ServerRail.vue), a guild picker, a modules summary and a logs panel.useActiveGuild()anduseDashboardNavigation()own that state./guilds/:id/manage/*stays as a legacy shim: it records which guild and section the old link pointed at, then forwards, so existing links keep working.Database.
schema.prismamoves onto the V7 contract —Modulesparents the settings tables, permission nodes are restored, and mention spam, configurable roles, language, disabled commands, public/unique roles and the moderator reason come back. Migration20260920000000_v7_contract_and_modulescreates the new shape, seeds aModulesrow per guild, and refuses to archive populated V6 tables before the bot has migrated them.Prisma ORM 8. Adopted side by side with ORM 7, per the upgrade guide.
prisma.config.tsdrives ORM 8 againstserver/database/prisma8/contract.prisma; the ORM 7 scripts keep their ownprisma7.config.ts. Every model carries@@control(external)because the bot owns the migrations — this project must never runprisma db sign. The three guild-log routes now query throughdb.orm.server/utils/audit/postgres-drain.tsdeliberately stays on ORM 7: it needsSerializableisolation and a serialization-failure retry to keep the audit hash chain from forking, and the ORM 8 façade exposes neither.Log route hardening. The command-log route returns a JSON-safe DTO instead of the Prisma model, so bigints never reach the wire. Snowflake filters are validated before
BigInt()rather than throwing a 500. Free-text search escapes%and_soq=_no longer matches everything.i18n. New keys for the nav descriptions, dashboard shell and guild settings, added across every locale as empty placeholders.
Fixed since the last review (commits
50e75f1,b6ff122,dd7303d):/oauth/guildwas throwinguseRouteQuery is not definedon every server render, and that 500 was what "Exiting due to prerender errors" was hiding.vp run build:testnow completes clean, which should return Browser tests, Performance and both Accessibility audits to green. See the note below — this bug is onmaintoo.guildSettingsChanges.spec.tspasses. It had been mockinguseRouteParamswhile the composable moved touseActiveGuild(), so the mock intercepted nothing and the two tests that did pass were passing vacuously.useDashboardNavigation().goToGuild(), and theas anyinModules.spec.tsis gone.Still open:
Typecheck is red, and it is staying red on this PR by decision. The V7 schema landed here but the code that reads it has not been ported, so it is being split into a follow-up rather than widened into this PR.
Guildno longer holds the settings — V7 decomposed it into ~15 tables parented byModules— so the derivedGuildDatacollapsed to{ id, language }and every settings read fails to compile. The affected code isAdderManager.ts(by far the largest share),SettingsContext.ts,PermissionNodeManager.ts,settings/types.ts,settings/context/functions.ts,utils/discord/index.ts,audit/postgres-drain.ts, thesettings/filter/*.vuecomponents and the guild-settings specs. The three commits above did not change this: they removed two errors and added none.The port needs a decision on how the dashboard composes one settings view across the new tables and how a PATCH is routed to the right table, which is the bot's to make — it owns the shared database, and every model in
prisma8/contract.prismacarries@@control(external). That is tracked on the four openschema.prismareview threads.Data migration is not in scope here: the V6→V7 backfill and the audit-chain carry-over belong to the bot.
Chromatic's UI Tests and UI Review sit pending on an org-wide plan limit — the same three statuses appear on
main, so they are not this PR's doing.Note: one fix here is for a bug on
mainapp/pages/oauth/callback.vueandapp/pages/oauth/guild.vueboth calleduseRouteQuery, which comes from@vueuse/router— a package this repo does not depend on. The identifier was never auto-imported, so both pages threw on every server render. Against a dev server,/oauth/guildand/oauth/callbackeach returned 500 while/oauth/loginreturned 200.This arrived in 9fdb68f (#351) on 2026-08-04 and is live on
main, where it breaks the post-sign-in landing. It is fixed here only because it was blocking this branch's prerender; it deserves a look independently of this PR.Generated by Claude Code

Confidence Score: 5/5
The change since the last review removes a comment without changing behavior; no additional merge blockers were identified, while the acknowledged V7 settings port and red typecheck remain deferred dependencies.Summary
This PR refreshes the marketing pages, moves the dashboard to
/app, and adopts the bot’s V7 database shape alongside Prisma ORM 8./appdashboard./app.Reviews (21) · Last reviewed commit: "test(build-environment): drop a comment ..." · Reviewed by Greptile