Skip to content

Refactor project structure, enhance UI components, and implement features - #43

Open
mrboxs wants to merge 12 commits into
marvticle-monorepofrom
feat/ui
Open

Refactor project structure, enhance UI components, and implement features#43
mrboxs wants to merge 12 commits into
marvticle-monorepofrom
feat/ui

Conversation

@mrboxs

@mrboxs mrboxs commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added user authentication with sign-in and onboarding flows
    • Implemented dark/light theme switching
    • Added thread voting functionality
    • Introduced user avatar generation with fallback UI
    • Added social sign-in options
  • UI/UX Improvements

    • Enhanced error and loading state presentations
    • Implemented route progress indicator
    • Updated navigation sidebar
    • Improved toast notifications

mrboxs added 12 commits May 24, 2026 22:53
- Add new UI components: dialog, empty, input, separator, sheet, sidebar, skeleton, tooltip, use-mobile hook
- Add app layout with _app route and theme provider
- Add error/loading/not-found/pending components
- Add TanStack Router progress indicator
- Update button component variant
- Restructure routes under _app layout
…components for toast notifications and cards
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Walkthrough

This PR transforms the frontend into a fully functional thread discussion application with user authentication, thread listing/voting, and a comprehensive UI component library. It introduces nested routing structures (_app, _auth), implements infinite-scroll thread pagination with optimistic vote caching, adds an onboarding flow, and provides 30+ reusable UI components built on Radix UI primitives.

Changes

Frontend Application Architecture & User Experience

Layer / File(s) Summary
Authentication and Onboarding
apps/web/src/libs/auth-client.ts, apps/web/src/routes/_auth/on-boarding.tsx, packages/auth/src/index.ts, packages/db/src/schemas/auth.ts
Better Auth client setup with username/admin/multi-session plugins; new onboarding route collecting username via Zod-validated form; auth schema extended with onBoardingCompleted boolean field and default-generated usernames.
App Shell with Sidebar, Navbar, and Providers
apps/web/src/routes/__root.tsx, apps/web/src/routes/_app.tsx, apps/web/src/components/sidebar-*, apps/web/src/components/navbar.tsx, apps/web/src/components/theme-provider.tsx
Root document wrapping with ThemeProvider (dark/light/system modes, localStorage persistence, FOUC prevention), TanStackRouterProgressProvider, TooltipProvider, Toaster; new _app parent route with onboarding guard; sidebar with collapsible state/cookie persistence and mobile sheet variant; navbar with sidebar toggle.
Thread Data Fetching & Voting
apps/web/src/libs/orpc.ts, apps/web/src/features/threads/utils/thread-query-options.ts, apps/web/src/features/votes/hooks/use-votes.ts, apps/web/src/features/votes/utils/votes-optimistic-cache.ts
ORPC client exports (threadORPC, replyORPC, voteORPC) for TanStack Query; infinite query options with cursor pagination; vote mutation with optimistic cache snapshots/restores, error/success toasts, and query invalidation.
Thread Feed Display & Voting UI
apps/web/src/routes/_app/index.tsx, apps/web/src/features/threads/components/thread-card.tsx, apps/web/src/features/threads/components/thread-list-card.tsx
Thread listing route with feed selection (discover/hot/trending) and prefetch via ensureInfiniteQueryData; thread card with author/timestamp/vote controls (filled/outline icons based on user vote state); list with infinite scroll, empty/loading-more/error states, and feed-specific messaging.
Auth Routes and Sign-in UI
apps/web/src/routes/_auth.tsx, apps/web/src/routes/_auth/sign-in.tsx, apps/web/src/components/sign-in-social-buttons.tsx
New _auth parent route with callbackURL normalization; sign-in page with social sign-in buttons (GitHub/Google) passing auth client provider and callback; back-to-home button in auth layout.
Root Routing, Default Components, and Theme Management
apps/web/src/routeTree.gen.ts, apps/web/src/router.tsx, apps/web/src/components/error-components.tsx, apps/web/src/components/not-found-components.tsx, apps/web/src/components/pending-components.tsx
Route tree restructured with _app/_auth nested hierarchy; router configured with default pending/not-found/error UI components; default error component with dev-only error detail dialog and retry button; default 404 and pending spinners.
Core UI Components (Primitives)
packages/ui/src/components/avatar.tsx, packages/ui/src/components/button.tsx, packages/ui/src/components/card.tsx, packages/ui/src/components/input.tsx, packages/ui/src/components/textarea.tsx, packages/ui/src/components/label.tsx, packages/ui/src/components/separator.tsx, packages/ui/src/components/empty.tsx
Radix UI wrappers and HTML element components with slot attributes and Tailwind styling; Avatar with size variants and fallback; Button with variant/size options and focus ring; Card with size-aware padding; Input/Textarea with validation state classes; Empty state components with icon/title/description layout.
Complex UI Components (Dialogs, Drawers, Dropdowns, Forms)
packages/ui/src/components/dialog.tsx, packages/ui/src/components/drawer.tsx, packages/ui/src/components/dropdown-menu.tsx, packages/ui/src/components/input-group.tsx, packages/ui/src/components/field.tsx, packages/ui/src/components/sidebar.tsx
Radix Dialog/vaul Drawer wrappers with optional close buttons; DropdownMenu with checkbox/radio items and nested sub-menus; InputGroup with addon/button variants and click-to-focus behavior; Field system with orientation variants and error deduplication via useMemo; complete Sidebar system with state provider, keyboard shortcuts, menu buttons with tooltips, and mobile sheet fallback.
Utility & Provider Components
packages/ui/src/components/sonner.tsx, packages/ui/src/components/spinner.tsx, packages/ui/src/components/skeleton.tsx, packages/ui/src/components/tooltip.tsx, packages/ui/src/components/button-group.tsx, packages/ui/src/hooks/use-mobile.ts, apps/web/src/components/avatar-user.tsx, apps/web/src/components/loader.tsx, apps/web/src/components/marv-icon.tsx
Sonner Toast provider with theme-aware styling and Tabler icon mappings; Spinner and Skeleton loaders; Tooltip with delay/sideOffset config; ButtonGroup with orientation variants; useIsMobile hook for 768px breakpoint; AvatarUser conditional rendering (GeneratedAvatar vs image-based), Loader.Inline with staggered dot animation, MarvIcon brand SVG.
Shared Schema & Type Updates
packages/shared/src/constants/index.ts, packages/shared/src/schemas/threads.ts, packages/shared/src/schemas/threads.test.ts, packages/shared/src/schemas/votes.ts
New pagination limit constants (default 10, max 50, min 1); thread schema .strict() with userVote field (replacing uservote); vote schema refactored from action-based to user-vote output (nullable direction + vote score count); updated tests for schema field name changes.
Backend Query & Database Schema Updates
packages/api/src/routers/threads.router.ts, packages/db/src/queries/thread.query.ts, packages/db/src/queries/reply.query.ts, packages/db/src/queries/vote.query.ts, packages/db/src/schemas/auth.ts
Thread router with typed error handling; query builders updated to select userVote (not uservote) with typed cursor shapes; vote queries refactored to return userVote/votesScoresCount instead of action-based output; auth schema gains onBoardingCompleted column.
Dependency Catalog and Configuration Updates
package.json, apps/web/package.json, packages/ui/package.json, packages/ui/components.json, opencode.json, apps/web/src/libs/query-client.ts
Workspace catalog pinned to caret versions for TanStack/React; new libraries added (BProgress, Dicebear, Sonner, date-fns, motion, react-icons); UI style config updated to radix-mira; OpenCode MCP config added for better-auth; Query client staleTime reduced to 30s, gcTime set to 5min.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

  • isntboxs/marvticle#39: Shares refactored thread schemas and pagination cursor logic with aligned field naming (userVote, pagination limits).
  • isntboxs/marvticle#36: Complements vote UI integration by implementing the vote toggle mutation and optimistic cache alongside schema changes.
  • isntboxs/marvticle#41: Backend votes routers and queries that this PR's frontend vote mutation and ORPC helpers depend on.

Poem

🐰 A rabbit hops through threads with glee,
Votes dance optimistically,
Dark mode whispers, forms appear,
Sixty components standing near,
Onboarding smiles, the feed takes flight—
A full-featured app, shining bright!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ui

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/auth/src/index.ts (1)

91-98: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Critical: username default is generated once, causing unique collisions

In packages/auth/src/index.ts (lines 91-98), defaultValue: \u_${crypto.randomUUID().slice(0, 8)}`is evaluated when the auth instance/config is created, so every user created without an explicitusernamewill share the sameusername. With unique: true`, this will make subsequent signups/creations fail.

Generate the username per user instead (e.g., via a user-creation hook/callback or a DB-side default/trigger), rather than relying on the current defaultValue expression.

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

In `@packages/auth/src/index.ts` around lines 91 - 98, The username field's
defaultValue (`defaultValue: \`u_${crypto.randomUUID().slice(0, 8)}\``) is
evaluated once at module load causing duplicate usernames; change generation to
run per-user instead by removing the static defaultValue and implementing a
per-creation generator (e.g., in the user model's creation hook such as a
beforeCreate/onCreate callback) that assigns `username` when absent, or use a
DB-side default/trigger; update the code referencing the username field (the
username property in packages/auth/src/index.ts) to rely on that hook/generator
so each new user receives a unique `u_<random>` value.
🧹 Nitpick comments (1)
packages/ui/src/styles/globals.css (1)

4-4: ⚡ Quick win

Drop the now-unused Outfit font import.

--font-sans is set to 'Inter Variable', and --font-heading maps to var(--font-sans), so Outfit is never referenced beyond @import '@fontsource-variable/outfit';. Removing it avoids shipping an unused font file.

♻️ Remove unused import
-@import '`@fontsource-variable/outfit`';
 `@import` '`@fontsource-variable/inter`';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/styles/globals.css` at line 4, The file contains an unused
font import "`@fontsource-variable/outfit`" that is never referenced because
--font-sans is set to "Inter Variable" and --font-heading uses var(--font-sans);
remove the `@import` '`@fontsource-variable/outfit`'; line from
packages/ui/src/styles/globals.css so the unused Outfit asset is no longer
shipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/components/error-components.tsx`:
- Around line 43-45: The effect currently depends on queryClientErrorBoundary
which may be a new object each render; change the useEffect in the error
component so queryClientErrorBoundary.reset() runs only on mount (replace the
dependency array [queryClientErrorBoundary] with []), or if ESLint complains,
keep the empty array and add a concise comment/disable for the specific
exhaustive-deps rule; reference the useEffect that calls
queryClientErrorBoundary.reset and the hook useQueryErrorResetBoundary() that
provides queryClientErrorBoundary.

In `@apps/web/src/components/navbar.tsx`:
- Around line 17-27: Navbar's aria-label and icon logic uses only state and
isMobile, causing the mobile button to always show the "Expand" variant; update
the condition to check openMobile when isMobile. Modify the JSX where aria-label
and icon are chosen (the conditional using state === 'collapsed' || isMobile) to
instead use (isMobile ? openMobile : state !== 'collapsed') or equivalent so
that when isMobile the label/icon reflect openMobile; ensure you reference the
openMobile boolean from the same props/state used by toggleSidebar and update
both the aria-label string and the icon selection (PanelLeftIcon vs
PanelLeftCloseIcon) accordingly.

In `@apps/web/src/components/sign-in-social-buttons.tsx`:
- Around line 18-29: The declaration of socialButtons uses "as const" which
makes a readonly tuple conflicting with the mutable Array<SocialButton> type;
remove the trailing "as const" from the socialButtons initializer (or
alternatively change the SocialButton type/array declaration to readonly) so the
literal array's mutability matches the declared type; locate the socialButtons
constant and adjust the "as const" usage or convert
SocialButton/Array<SocialButton> to readonly to resolve the type mismatch.

In `@apps/web/src/features/threads/components/thread-card.tsx`:
- Around line 92-97: The Link for the thread title in thread-card.tsx currently
uses to="." which keeps users on the current list page; change it to point to
the thread detail route by replacing the to="." on the Link (in
apps/web/src/features/threads/components/thread-card.tsx) with the correct
destination for a single thread—either use the generated route helper from
apps/web/src/routeTree.gen.ts (e.g., the route helper for the thread detail)
supplying thread.slug, or if no helper exists add a thread detail route (e.g.,
/threads/$slug) to the route tree and then link using that route with
thread.slug so clicking the title navigates to the thread detail page instead of
the current route.

In `@apps/web/src/routes/_auth.tsx`:
- Around line 8-13: The sanitizeCallbackURL function currently allows inputs
like "/\evil.com" because it only checks startsWith('/') and not for
backslashes; update sanitizeCallbackURL to explicitly reject or normalize any
URL containing backslash characters before accepting it—e.g., if url contains
'\\' return '/' (or normalize by replacing backslashes then re-validate), and
keep the existing checks for url.startsWith('/') and !url.startsWith('//') so
that inputs with backslashes cannot be transformed by browser normalization into
protocol-relative or external URLs; refer to the sanitizeCallbackURL function to
implement this validation.

In `@apps/web/src/routes/_auth/on-boarding.tsx`:
- Around line 47-56: The regex in onBoardingFormSchema.username uses {1,28}
which yields total length 2–29, conflicting with .min(3) and .max(30); update
the quantifier in the regex (the pattern in onBoardingFormSchema for username)
to {2,29} so the regex enforces total length 3–30 and still preserves the
start/end and hyphen rules.

In `@packages/ui/src/components/dialog.tsx`:
- Line 4: The import in components/dialog.tsx is using the wrong Radix package
path; replace the incorrect import from 'radix-ui' with the scoped package
'`@radix-ui/react-dialog`' so the Dialog component (imported as Dialog or
DialogPrimitive) resolves correctly; locate the import statement referencing
Dialog (e.g., "import { Dialog as DialogPrimitive }") and update its module
specifier to '`@radix-ui/react-dialog`' and then run the repo type-check/build to
confirm the component compiles.

In `@packages/ui/src/components/separator.tsx`:
- Around line 18-21: The Tailwind data selectors are wrong for
SeparatorPrimitive.Root: replace the current data-horizontal:* and
data-vertical:* utilities in the className passed to the Separator component
with Tailwind data attribute selectors that match Radix's data-orientation, e.g.
use data-[orientation=horizontal]:h-px and data-[orientation=horizontal]:w-full
and data-[orientation=vertical]:w-px and
data-[orientation=vertical]:self-stretch (keep the cn(...) wrapper and preserve
the existing 'shrink-0 bg-border' and the passed className).

---

Outside diff comments:
In `@packages/auth/src/index.ts`:
- Around line 91-98: The username field's defaultValue (`defaultValue:
\`u_${crypto.randomUUID().slice(0, 8)}\``) is evaluated once at module load
causing duplicate usernames; change generation to run per-user instead by
removing the static defaultValue and implementing a per-creation generator
(e.g., in the user model's creation hook such as a beforeCreate/onCreate
callback) that assigns `username` when absent, or use a DB-side default/trigger;
update the code referencing the username field (the username property in
packages/auth/src/index.ts) to rely on that hook/generator so each new user
receives a unique `u_<random>` value.

---

Nitpick comments:
In `@packages/ui/src/styles/globals.css`:
- Line 4: The file contains an unused font import "`@fontsource-variable/outfit`"
that is never referenced because --font-sans is set to "Inter Variable" and
--font-heading uses var(--font-sans); remove the `@import`
'`@fontsource-variable/outfit`'; line from packages/ui/src/styles/globals.css so
the unused Outfit asset is no longer shipped.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2ccb4281-4e30-4263-9481-98b107befb54

📥 Commits

Reviewing files that changed from the base of the PR and between e9984c1 and 53d11bc.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (74)
  • apps/web/package.json
  • apps/web/src/components/avatar-user.tsx
  • apps/web/src/components/error-components.tsx
  • apps/web/src/components/generated-avatar.tsx
  • apps/web/src/components/loader.tsx
  • apps/web/src/components/marv-icon.tsx
  • apps/web/src/components/navbar.tsx
  • apps/web/src/components/not-found-components.tsx
  • apps/web/src/components/pending-components.tsx
  • apps/web/src/components/sidebar-content.tsx
  • apps/web/src/components/sidebar-footer.tsx
  • apps/web/src/components/sidebar-header.tsx
  • apps/web/src/components/sidebar-wrapper.tsx
  • apps/web/src/components/sign-in-social-buttons.tsx
  • apps/web/src/components/tanstack-router-progress-provider.tsx
  • apps/web/src/components/tanstack-router-progress.tsx
  • apps/web/src/components/theme-provider.tsx
  • apps/web/src/features/threads/components/thread-card.tsx
  • apps/web/src/features/threads/components/thread-list-card.tsx
  • apps/web/src/features/threads/hooks/use-threads.ts
  • apps/web/src/features/threads/utils/thread-query-options.ts
  • apps/web/src/features/votes/hooks/use-votes.ts
  • apps/web/src/features/votes/utils/votes-optimistic-cache.ts
  • apps/web/src/integrations/tanstack-query/devtools.tsx
  • apps/web/src/integrations/tanstack-query/root-provider.tsx
  • apps/web/src/libs/auth-client.ts
  • apps/web/src/libs/orpc.ts
  • apps/web/src/libs/query-client.ts
  • apps/web/src/routeTree.gen.ts
  • apps/web/src/router.tsx
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_app.tsx
  • apps/web/src/routes/_app/index.tsx
  • apps/web/src/routes/_auth.tsx
  • apps/web/src/routes/_auth/on-boarding.tsx
  • apps/web/src/routes/_auth/sign-in.tsx
  • apps/web/src/routes/index.tsx
  • opencode.json
  • package.json
  • packages/api/src/routers/threads.router.ts
  • packages/api/src/routers/votes.router.ts
  • packages/auth/src/index.ts
  • packages/db/src/queries/reply.query.ts
  • packages/db/src/queries/thread.query.ts
  • packages/db/src/queries/vote.query.ts
  • packages/db/src/schemas/auth.ts
  • packages/shared/src/constants/index.ts
  • packages/shared/src/schemas/threads.test.ts
  • packages/shared/src/schemas/threads.ts
  • packages/shared/src/schemas/votes.ts
  • packages/ui/components.json
  • packages/ui/package.json
  • packages/ui/src/components/avatar.tsx
  • packages/ui/src/components/button-group.tsx
  • packages/ui/src/components/button.tsx
  • packages/ui/src/components/card.tsx
  • packages/ui/src/components/dialog.tsx
  • packages/ui/src/components/drawer.tsx
  • packages/ui/src/components/dropdown-menu.tsx
  • packages/ui/src/components/empty.tsx
  • packages/ui/src/components/field.tsx
  • packages/ui/src/components/input-group.tsx
  • packages/ui/src/components/input.tsx
  • packages/ui/src/components/label.tsx
  • packages/ui/src/components/separator.tsx
  • packages/ui/src/components/sheet.tsx
  • packages/ui/src/components/sidebar.tsx
  • packages/ui/src/components/skeleton.tsx
  • packages/ui/src/components/sonner.tsx
  • packages/ui/src/components/spinner.tsx
  • packages/ui/src/components/textarea.tsx
  • packages/ui/src/components/tooltip.tsx
  • packages/ui/src/hooks/use-mobile.ts
  • packages/ui/src/styles/globals.css
💤 Files with no reviewable changes (3)
  • apps/web/src/routes/index.tsx
  • apps/web/src/integrations/tanstack-query/root-provider.tsx
  • apps/web/src/integrations/tanstack-query/devtools.tsx

Comment on lines +43 to +45
useEffect(() => {
queryClientErrorBoundary.reset()
}, [queryClientErrorBoundary])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Review the useEffect dependency array.

The useEffect includes queryClientErrorBoundary in the dependency array. If useQueryErrorResetBoundary() returns a new object reference on each render, this effect will run repeatedly instead of only once on mount. Typically, the error boundary reset should only run once when the error component mounts.

♻️ Suggested fix
 useEffect(() => {
   queryClientErrorBoundary.reset()
-}, [queryClientErrorBoundary])
+  // eslint-disable-next-line react-hooks/exhaustive-deps
+}, [])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
queryClientErrorBoundary.reset()
}, [queryClientErrorBoundary])
useEffect(() => {
queryClientErrorBoundary.reset()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/error-components.tsx` around lines 43 - 45, The
effect currently depends on queryClientErrorBoundary which may be a new object
each render; change the useEffect in the error component so
queryClientErrorBoundary.reset() runs only on mount (replace the dependency
array [queryClientErrorBoundary] with []), or if ESLint complains, keep the
empty array and add a concise comment/disable for the specific exhaustive-deps
rule; reference the useEffect that calls queryClientErrorBoundary.reset and the
hook useQueryErrorResetBoundary() that provides queryClientErrorBoundary.

Comment on lines +17 to +27
aria-label={
state === 'collapsed' || isMobile
? 'Expand sidebar'
: 'Collapse sidebar'
}
>
{state === 'collapsed' || isMobile ? (
<PanelLeftIcon />
) : (
<PanelLeftCloseIcon />
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify useSidebar exposes openMobile/state so the toggle can reflect the actual open state on mobile.
ast-grep --pattern 'const useSidebar = () => { $$$ }'
rg -nP '\b(openMobile|isMobile|state)\b' packages/ui/src/components/sidebar.tsx -C2

Repository: isntboxs/marvticle

Length of output: 3623


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the navbar toggle code around the referenced lines
echo "---- navbar.tsx (lines 1-80) ----"
nl -ba apps/web/src/components/navbar.tsx | sed -n '1,120p'

# Find where openMobile is referenced/consumed in navbar.tsx
echo "---- search openMobile usage in navbar.tsx ----"
rg -n "openMobile|setOpenMobile" apps/web/src/components/navbar.tsx || true

# Locate how navbar imports/uses the sidebar hook/context
echo "---- search for useSidebar usage in navbar.tsx ----"
rg -n "useSidebar" apps/web/src/components/navbar.tsx

# Inspect sidebar hook return shape for what navbar might be able to use
echo "---- sidebar implementation snippet (useSidebar context) ----"
rg -n "openMobile|isMobile|toggleSidebar" packages/ui/src/components/sidebar.tsx -n

Repository: isntboxs/marvticle

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show navbar.tsx with line numbers (using awk instead of nl)
echo "---- navbar.tsx (first 160 lines) ----"
awk '{printf "%5d\t%s\n", NR, $0}' apps/web/src/components/navbar.tsx | sed -n '1,160p'

echo "---- search openMobile usage in navbar.tsx ----"
rg -n "openMobile|setOpenMobile" apps/web/src/components/navbar.tsx || true

echo "---- search useSidebar usage in navbar.tsx ----"
rg -n "useSidebar" apps/web/src/components/navbar.tsx || true

echo "---- show relevant snippet around the toggle (look for Expand/Collapse sidebar strings) ----"
rg -n "Expand sidebar|Collapse sidebar" apps/web/src/components/navbar.tsx -n -C3 || true

Repository: isntboxs/marvticle

Length of output: 1710


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant parts of sidebar.tsx: context shape, state/openMobile derivation, and isMobile computation
echo "---- sidebar.tsx: SidebarContextProps + hook return (first 220 lines) ----"
awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/components/sidebar.tsx | sed -n '1,240p'

echo "---- sidebar.tsx: useIsMobile + toggleSidebar + state/openMobile (around toggle/state) ----"
rg -n "useIsMobile|toggleSidebar|openMobile|const state" packages/ui/src/components/sidebar.tsx -n
echo "---- sidebar.tsx: exact lines around openMobile & state (lines 55-140) ----"
awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/components/sidebar.tsx | sed -n '50,160p'

echo "---- sidebar.tsx: mobile rendering uses openMobile (around isMobile block) ----"
awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/components/sidebar.tsx | sed -n '160,230p'

Repository: isntboxs/marvticle

Length of output: 15306


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "function useIsMobile|export const useIsMobile|export function useIsMobile" -S packages/ui/src/hooks/use-mobile* packages/ui/src/hooks -g'*.ts' -g'*.tsx'

Repository: isntboxs/marvticle

Length of output: 202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

awk '{printf "%5d\t%s\n", NR, $0}' packages/ui/src/hooks/use-mobile.ts | sed -n '1,120p'

Repository: isntboxs/marvticle

Length of output: 722


Fix toggle label/icon to reflect mobile sidebar open state.

On mobile, isMobile is driven by viewport width, so Navbar’s condition (state === 'collapsed' || isMobile ? 'Expand sidebar' : 'Collapse sidebar' and the icon) always picks the “Expand” variant even when the mobile Sheet is open. toggleSidebar toggles openMobile, but Navbar only reads state and isMobile.

Update Navbar to also use openMobile and switch the aria-label/icon based on openMobile when isMobile.

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

In `@apps/web/src/components/navbar.tsx` around lines 17 - 27, Navbar's aria-label
and icon logic uses only state and isMobile, causing the mobile button to always
show the "Expand" variant; update the condition to check openMobile when
isMobile. Modify the JSX where aria-label and icon are chosen (the conditional
using state === 'collapsed' || isMobile) to instead use (isMobile ? openMobile :
state !== 'collapsed') or equivalent so that when isMobile the label/icon
reflect openMobile; ensure you reference the openMobile boolean from the same
props/state used by toggleSidebar and update both the aria-label string and the
icon selection (PanelLeftIcon vs PanelLeftCloseIcon) accordingly.

Comment on lines +18 to +29
const socialButtons: Array<SocialButton> = [
{
provider: 'github',
icon: FaGithub,
label: 'GitHub',
},
{
provider: 'google',
icon: FcGoogle,
label: 'Google',
},
] as const

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the file compiles; expect a readonly-vs-mutable assignment error if `as const` is kept with Array<SocialButton>.
fd -t f tsconfig.json apps/web
rg -nP 'as const' apps/web/src/components/sign-in-social-buttons.tsx

Repository: isntboxs/marvticle

Length of output: 99


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/components/sign-in-social-buttons.tsx"

echo "---- sign-in-social-buttons.tsx (relevant lines) ----"
nl -ba "$FILE" | sed -n '1,120p'

echo "---- Find SocialButton definition/usages ----"
rg -n --hidden --no-ignore -S "type\s+SocialButton|interface\s+SocialButton|SocialButton" apps/web/src | head -n 50

echo "---- Inspect the inferred type of socialButtons by searching its declaration context ----"
rg -n "socialButtons\s*:" "$FILE"

echo "---- Check if socialButtons is consumed in a way that requires readonly or mutable array ----"
rg -n --hidden --no-ignore -S "socialButtons" apps/web/src/components/sign-in-social-buttons.tsx

Repository: isntboxs/marvticle

Length of output: 177


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/components/sign-in-social-buttons.tsx"

echo "---- sign-in-social-buttons.tsx (relevant lines) ----"
cat -n "$FILE" | sed -n '1,160p'

echo "---- Find SocialButton definition/usages ----"
rg -n --hidden --no-ignore -S "type\s+SocialButton|interface\s+SocialButton|class\s+SocialButton|enum\s+SocialButton|\bSocialButton\b" apps/web/src | head -n 80

echo "---- Find socialButtons declaration context ----"
rg -n "socialButtons\s*:" "$FILE"

echo "---- Check if socialButtons is consumed in a way that requires readonly or mutable array ----"
rg -n --hidden --no-ignore -S "\bsocialButtons\b" "$FILE" apps/web/src | head -n 80

Repository: isntboxs/marvticle

Length of output: 2855


Fix readonly as const vs mutable Array<SocialButton> mismatch in socialButtons

as const turns the array into a readonly tuple with readonly element properties, which can’t be assigned to the mutable Array<SocialButton> (where SocialButton’s fields are not readonly). Remove as const (or make the SocialButton/array types readonly consistently).

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

In `@apps/web/src/components/sign-in-social-buttons.tsx` around lines 18 - 29, The
declaration of socialButtons uses "as const" which makes a readonly tuple
conflicting with the mutable Array<SocialButton> type; remove the trailing "as
const" from the socialButtons initializer (or alternatively change the
SocialButton type/array declaration to readonly) so the literal array's
mutability matches the declared type; locate the socialButtons constant and
adjust the "as const" usage or convert SocialButton/Array<SocialButton> to
readonly to resolve the type mismatch.

Comment on lines +92 to +97
<Link
to="."
className="text-xl leading-tight font-bold transition-all duration-500 ease-in-out hover:text-primary/90"
>
{thread.title}
</Link>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the thread detail route definition and its expected params
fd -e tsx . apps/web/src/routes | xargs rg -nl 'slug' 2>/dev/null
rg -nP 'createFileRoute\(' -g 'apps/web/src/routes/**' -C2

Repository: isntboxs/marvticle

Length of output: 2415


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the thread-card link usage around the reported lines
sed -n '70,120p' apps/web/src/features/threads/components/thread-card.tsx | nl -ba

# 2) Find any routes that look like thread detail (param names like $slug/$id)
fd -e tsx apps/web/src/routes | rg -n --hidden -S "(threads|thread)" | head -n 50

# 3) Specifically search for createFileRoute paths containing likely thread params
rg -n "createFileRoute\\('(/[^']*thread[^']*|/[^']*threads[^']*)'" apps/web/src/routes --glob='*.tsx' || true

# 4) Search for dynamic params used in routes
rg -n "\\$slug|\\$id|\\$thread|slug\\b" apps/web/src/routes --glob='*.tsx' | head -n 80

# 5) Show any routes under threads/ directory (if it exists)
ls -la apps/web/src/routes || true
find apps/web/src/routes -maxdepth 3 -type f -name '*thread*' -o -name '*threads*' | sed 's|^|FOUND: |' || true

Repository: isntboxs/marvticle

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the thread-card link usage around the reported lines
echo "---- thread-card.tsx (lines 70-120) ----"
sed -n '70,120p' apps/web/src/features/threads/components/thread-card.tsx | cat -n

# 2) Find any route files that look like thread detail
echo "---- candidate route files under apps/web/src/routes ----"
fd -e tsx 'thread*.tsx' apps/web/src/routes apps/web/src/routes || true
fd -e tsx '*thread*' apps/web/src/routes || true
fd -e tsx '*threads*' apps/web/src/routes || true

# 3) Search for dynamic params used in routes
echo "---- route dynamic params: $slug/$id/$thread ----"
rg -n --hidden "\\$slug|\\$id|\\$thread" apps/web/src/routes --glob='*.tsx' || true

# 4) Search for createFileRoute that may represent thread detail pages
echo "---- createFileRoute paths containing thread/threads ----"
rg -n --hidden "createFileRoute\\('(/[^']*(thread|threads)[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || true

Repository: isntboxs/marvticle

Length of output: 2431


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- thread-card.tsx (lines 1-220) ----"
sed -n '1,220p' apps/web/src/features/threads/components/thread-card.tsx | cat -n

echo
echo "---- list routes (depth 4) ----"
find apps/web/src/routes -maxdepth 4 -type f | sed 's|^|ROUTE: |' | head -n 200

echo
echo "---- route dynamic params in routes (look for $something) ----"
rg -n --hidden '\\$[A-Za-z_][A-Za-z0-9_]*' apps/web/src/routes --glob='*.tsx' || true

echo
echo "---- route files mentioning 'thread' or 'threads' ----"
rg -n --hidden 'threads|thread' apps/web/src/routes --glob='*.tsx' | head -n 200 || true

echo
echo "---- candidate createFileRoute('...') paths containing thread/threads ----"
rg -n --hidden "createFileRoute\\('(/[^']*(thread|threads)[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || true

Repository: isntboxs/marvticle

Length of output: 6571


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- total route tsx files ----"
find apps/web/src/routes -type f -name '*.tsx' | wc -l

echo
echo "---- route files with 'thread'/'threads' in filename ----"
fd --type f -e tsx --glob '*thread*' apps/web/src/routes || true
fd --type f -e tsx --glob '*threads*' apps/web/src/routes || true

echo
echo "---- routes whose createFileRoute path mentions thread/threads ----"
rg -n "createFileRoute\\('([^']*(thread|threads)[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || true

echo
echo "---- dynamic params in route definitions ($slug-like) ----"
rg -n --hidden "\\$[A-Za-z_][A-Za-z0-9_]*" apps/web/src/routes --glob='*.tsx' || true

echo
echo "---- show all route createFileRoute paths that contain /threads/ ----"
rg -n "createFileRoute\\('([^']*/threads/[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || true

echo
echo "---- show all route createFileRoute paths that contain /thread/ ----"
rg -n "createFileRoute\\('([^']*/thread/[^']*)'\\)" apps/web/src/routes --glob='*.tsx' || true

Repository: isntboxs/marvticle

Length of output: 286


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- count of all route files (any extension) ----"
find apps/web/src/routes -type f | wc -l

echo
echo "---- all files under apps/web/src/routes containing 'thread'/'threads' (any ext) ----"
rg -n --hidden --no-ignore -S "threads|thread" apps/web/src/routes || true

echo
echo "---- createFileRoute occurrences under apps/web/src/routes (all ext) ----"
rg -n --hidden "createFileRoute\\('" apps/web/src/routes || true

echo
echo "---- search for likely slug params in routes without using '$' in bash strings ----"
rg -n --hidden "slug" apps/web/src/routes || true

Repository: isntboxs/marvticle

Length of output: 2192


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- repo-wide search for thread detail route patterns ----"
rg -n --hidden --no-ignore -S "createFileRoute\\('([^']*(thread|threads)[^']*)'\\)" . || true

echo
echo "---- repo-wide search for route paths containing /threads/ or /thread/ ----"
rg -n --hidden --no-ignore -S "['\"]/(threads|thread)/\\$" . || true

echo
echo "---- repo-wide search for tanstack route usage with $slug ----"
rg -n --hidden --no-ignore -S "to=\\{?.*\\$slug|\\$slug" apps/web/src . || true

echo
echo "---- search for Thread detail route naming in app (components/hooks/routes) ----"
rg -n --hidden --no-ignore -S "(thread detail|thread-details|ThreadDetails|ThreadDetail)" apps/web/src || true

echo
echo "---- search for thread-card Link targets in other components ----"
rg -n --hidden --no-ignore -S "thread-card|to=\"\\.\"|to=\"\\.\"" apps/web/src/features/threads || true

Repository: isntboxs/marvticle

Length of output: 261


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- repo-wide search for TanStack route definitions that mention thread(s) ----"
rg -n --hidden --no-ignore -S "createFileRoute\\('([^']*(thread|threads)[^']*)'\\)" . || true

echo
echo "---- repo-wide search for route path segments containing /threads/ or /thread/ (string literals) ----"
rg -n --hidden --no-ignore -S "['\"]/threads/|['\"]/thread/" . || true

echo
echo "---- search for thread-detail style route patterns (e.g., /threads/$..., threads/$...) ----"
rg -n --hidden --no-ignore -S "/threads/\\$|threads/\\$|/thread/\\$|thread/\\$" . || true

echo
echo "---- search for thread-card Link targets patterns in repo ----"
rg -n --hidden --no-ignore -S "features/threads|ThreadCard|to=\"\\.\"|to='\\.'|viewTransition" apps/web/src || true

echo
echo "---- list all createFileRoute paths (to see what routes exist) ----"
rg -n --hidden --no-ignore -S "createFileRoute\\('" apps/web/src -g'*.ts*' || true

Repository: isntboxs/marvticle

Length of output: 3916


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- createFileRoute occurrences anywhere in repo ----"
rg -n --hidden --no-ignore "createFileRoute\\('" . || true

echo
echo "---- Link targets that look like thread detail: /threads/<slug> or /threads/ ----"
rg -n --hidden --no-ignore -S "to=\\\"/threads/\\$\\{|to=\\{\\s*`/threads/\\$\\{|to=\\\"/threads/|to=\\{\\s*`/threads/" apps/web/src || true
rg -n --hidden --no-ignore -S "to=\\\"/threads/|to=\\{.*\\/threads\\/" apps/web/src || true
rg -n --hidden --no-ignore -S "/threads/\\$\\{?slug\\}?|/threads/\\{slug\\}" apps/web/src || true

echo
echo "---- router setup: find Route definitions / router provider wiring ----"
rg -n --hidden --no-ignore -S "RouterProvider|routeTree|createRouter|useRouter\\(" apps/web/src || true
rg -n --hidden --no-ignore -S "routeTree\\.|generatedRouteTree|__routeTree|routeTree\\.ts" apps/web/src || true

echo
echo "---- inspect thread-list-card to see what the page route is for list ----"
sed -n '1,220p' apps/web/src/features/threads/components/thread-list-card.tsx | cat -n

Repository: isntboxs/marvticle

Length of output: 9054


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- routeTree.gen.ts: search for /threads and threads ----"
rg -n --hidden --no-ignore -S "/threads|threads" apps/web/src/routeTree.gen.ts || true

echo
echo "---- routeTree.gen.ts: search for slug param in routeTree ----"
rg -n --hidden --no-ignore -S "slug" apps/web/src/routeTree.gen.ts || true

echo
echo "---- full repo: search for routeTree.gen references containing threads ----"
rg -n --hidden --no-ignore -S "routeTree\\.gen|routeTree" apps/web/src | head -n 50

Repository: isntboxs/marvticle

Length of output: 448


Fix thread title navigation (currently links to the current route)

In apps/web/src/features/threads/components/thread-card.tsx (lines 92-97), the thread title Link uses to=".", so it resolves to the current page (thread list) instead of a thread detail page.

<Link
  to="."
  className="text-xl leading-tight font-bold transition-all duration-500 ease-in-out hover:text-primary/90"
>
  {thread.title}
</Link>

The client route tree (apps/web/src/routes / apps/web/src/routeTree.gen.ts) doesn’t include a thread detail route (e.g. /threads/$slug), so the fix is to either add that route and link it using thread.slug, or update this Link to the correct existing thread-detail destination.

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

In `@apps/web/src/features/threads/components/thread-card.tsx` around lines 92 -
97, The Link for the thread title in thread-card.tsx currently uses to="." which
keeps users on the current list page; change it to point to the thread detail
route by replacing the to="." on the Link (in
apps/web/src/features/threads/components/thread-card.tsx) with the correct
destination for a single thread—either use the generated route helper from
apps/web/src/routeTree.gen.ts (e.g., the route helper for the thread detail)
supplying thread.slug, or if no helper exists add a thread detail route (e.g.,
/threads/$slug) to the route tree and then link using that route with
thread.slug so clicking the title navigates to the thread detail page instead of
the current route.

Comment on lines +8 to +13
function sanitizeCallbackURL(url: string): string {
if (!url.startsWith('/') || url.startsWith('//')) {
return '/'
}
return url
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Harden sanitizeCallbackURL against backslash open-redirect bypass.

Values like /\evil.com or /\/evil.com start with a single / and pass this check, but many browsers normalize backslashes to /, turning them into protocol-relative URLs. Normalize/reject backslashes too.

🛡️ Proposed fix
 function sanitizeCallbackURL(url: string): string {
-	if (!url.startsWith('/') || url.startsWith('//')) {
+	if (!url.startsWith('/') || url.startsWith('//') || /^\/[\\/]/.test(url)) {
 		return '/'
 	}
 	return url
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function sanitizeCallbackURL(url: string): string {
if (!url.startsWith('/') || url.startsWith('//')) {
return '/'
}
return url
}
function sanitizeCallbackURL(url: string): string {
if (!url.startsWith('/') || url.startsWith('//') || /^\/[\\/]/.test(url)) {
return '/'
}
return url
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/routes/_auth.tsx` around lines 8 - 13, The sanitizeCallbackURL
function currently allows inputs like "/\evil.com" because it only checks
startsWith('/') and not for backslashes; update sanitizeCallbackURL to
explicitly reject or normalize any URL containing backslash characters before
accepting it—e.g., if url contains '\\' return '/' (or normalize by replacing
backslashes then re-validate), and keep the existing checks for
url.startsWith('/') and !url.startsWith('//') so that inputs with backslashes
cannot be transformed by browser normalization into protocol-relative or
external URLs; refer to the sanitizeCallbackURL function to implement this
validation.

Comment on lines +47 to +56
const onBoardingFormSchema = z.object({
username: z
.string()
.min(3, 'Username must be at least 3 characters long')
.max(30, 'Username must be at most 30 characters long')
.regex(
/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,28}$/i,
'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number'
),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Regex upper bound conflicts with max(30).

The regex group {1,28} caps total length at 29 chars (1 leading + 1–28), while .max(30) permits 30. A 30-char alphanumeric username passes max but fails the regex, surfacing the misleading "at least 3 characters" message. Align the bounds.

🔧 Proposed fix
 		.regex(
-			/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,28}$/i,
+			/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,29}$/i,
 			'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number'
 		),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onBoardingFormSchema = z.object({
username: z
.string()
.min(3, 'Username must be at least 3 characters long')
.max(30, 'Username must be at most 30 characters long')
.regex(
/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,28}$/i,
'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number'
),
})
const onBoardingFormSchema = z.object({
username: z
.string()
.min(3, 'Username must be at least 3 characters long')
.max(30, 'Username must be at most 30 characters long')
.regex(
/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,29}$/i,
'Username must be at least 3 characters long, can only contain letters, numbers, and hyphens, and must start and end with a letter or number'
),
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/routes/_auth/on-boarding.tsx` around lines 47 - 56, The regex in
onBoardingFormSchema.username uses {1,28} which yields total length 2–29,
conflicting with .min(3) and .max(30); update the quantifier in the regex (the
pattern in onBoardingFormSchema for username) to {2,29} so the regex enforces
total length 3–30 and still preserves the start/end and hyphen rules.

import * as React from 'react'

import { IconX } from '@tabler/icons-react'
import { Dialog as DialogPrimitive } from 'radix-ui'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix incorrect Radix UI import path.

The import path 'radix-ui' appears incorrect. Radix UI packages are scoped under @radix-ui/. The correct import should be '@radix-ui/react-dialog'.

🐛 Proposed fix
-import { Dialog as DialogPrimitive } from 'radix-ui'
+import * as DialogPrimitive from '`@radix-ui/react-dialog`'

Run the following script to verify the correct Radix UI package:

#!/bin/bash
# Description: Check for Radix UI Dialog package in dependencies

# Check package.json for radix-ui dependencies
fd -t f 'package.json' --exec jq -r '.dependencies, .devDependencies | to_entries[] | select(.key | contains("radix")) | "\(.key): \(.value)"' {} \;

# Search for other Radix UI imports in the codebase
rg -n '`@radix-ui/react-`' --type=typescript --type=tsx -g '*.{ts,tsx}' | head -20
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ui/src/components/dialog.tsx` at line 4, The import in
components/dialog.tsx is using the wrong Radix package path; replace the
incorrect import from 'radix-ui' with the scoped package
'`@radix-ui/react-dialog`' so the Dialog component (imported as Dialog or
DialogPrimitive) resolves correctly; locate the import statement referencing
Dialog (e.g., "import { Dialog as DialogPrimitive }") and update its module
specifier to '`@radix-ui/react-dialog`' and then run the repo type-check/build to
confirm the component compiles.

Comment on lines +18 to +21
className={cn(
'shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch',
className
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for custom-variant definitions for data-horizontal/data-vertical
fd -e css | xargs rg -n -C1 'custom-variant|data-horizontal|data-vertical|data-orientation' 2>/dev/null

Repository: isntboxs/marvticle

Length of output: 251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the exact classes used in the separator component
echo "---- separator.tsx ----"
sed -n '1,120p' packages/ui/src/components/separator.tsx

# 2) Inspect globals.css for any custom variants or data-orientation handling
echo "---- globals.css (relevant parts) ----"
sed -n '1,120p' packages/ui/src/styles/globals.css

# 3) Search for any other definitions of custom variants or Tailwind config notes
echo "---- repo search for custom variants / data-orientation / data-horizontal / data-vertical ----"
rg -n "custom-variant|data-orientation|data-horizontal|data-vertical" packages/ui/src -S || true
rg -n "tailwind\.config|`@tailwind`|variants" -S . || true

Repository: isntboxs/marvticle

Length of output: 16601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- packages/ui/src/components/field.tsx (relevant parts) ----"
sed -n '1,220p' packages/ui/src/components/field.tsx

echo "---- packages/ui/src/components/button-group.tsx (relevant parts) ----"
sed -n '1,140p' packages/ui/src/components/button-group.tsx

Repository: isntboxs/marvticle

Length of output: 7891


🌐 Web query:

Tailwind CSS v4 variant syntax "data-horizontal:" what selector does it generate?

💡 Result:

In Tailwind CSS v4, the variant syntax data-horizontal: is not a built-in default; it is a custom variant that must be explicitly defined in your CSS [1][2][3]. When encountered in projects (such as early versions of some UI library components), it is typically defined as a shorthand for the data-orientation="horizontal" attribute selector [3]. If defined in your CSS file, it generates the following CSS selector: css &[data-orientation="horizontal"] You can define this custom variant in your CSS using the @custom-variant directive [4][5]: css `@custom-variant` data-horizontal (&[data-orientation="horizontal"]); If this variant is used in your project without being defined, it will not match any elements, often resulting in styles failing to apply [2][6]. Many modern component libraries have migrated away from these shorthands in favor of the standard, built-in Tailwind data-* syntax, which allows you to target any data attribute directly without custom configuration [7][8]: html <div class="data-[orientation=horizontal]:bg-gray-100"></div> The native data-[key=value] syntax automatically generates the corresponding [data-key="value"] selector [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search entire repo for custom-variant definitions for data-horizontal/data-vertical
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:" -S . || true

# Also look for any file that defines shadcn/tailwind.css locally in the repo
fd -t f "tailwind.css" -S . | rg -n "shadcn" || true

Repository: isntboxs/marvticle

Length of output: 685


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Search for any custom-variant definitions for data-horizontal/data-vertical
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:" -S .

# 2) Find any local tailwind.css files in the repo (to see if shadcn/tailwind.css is vendored)
fd -t f -e css -e js -e ts "tailwind.css" -S . || true
fd -t f -e css "tailwind.css" -S . || true

# 3) Narrow search for shadcn/tailwind.css file path if present
fd -t f "tailwind.css" -S . | rg -n "shadcn|tailwind\.css" || true

Repository: isntboxs/marvticle

Length of output: 953


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any custom-variant definitions for data-horizontal/data-vertical
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:" -S . || true

# Find any local tailwind.css files (could contain shadcn/tailwind.css vendored content)
fd -t f -e css -e js -e ts "tailwind.css" . || true
fd -t f -e css "shadcn/tailwind.css" . || true

# If any tailwind.css was found, scan them for custom variants
for f in $(fd -t f -e css "tailwind.css" .); do
  echo "---- $f ----"
  rg -n "`@custom-variant`|data-horizontal|data-vertical|data-orientation" "$f" -S || true
done

Repository: isntboxs/marvticle

Length of output: 533


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- tailwind.css files in repo (if any) ----"
fd -t f "tailwind.css" . || true

echo "---- shadcn directory/files in repo (if any) ----"
fd -t d "shadcn" . || true
fd -t f "shadcn" . || true

echo "---- any file defining custom variants for data-horizontal/data-vertical ----"
rg -n "`@custom-variant`" . -S || true
rg -n "data-horizontal|data-vertical|data-orientation" packages/ui/src/styles -S || true

Repository: isntboxs/marvticle

Length of output: 313


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Try to locate the imported shadcn/tailwind.css within the repository (including node_modules if present)
rg -n "shadcn/tailwind\.css" -S . || true

# Find any tailwind.css that mentions data-horizontal/data-vertical/data-orientation
fd -t f "tailwind.css" . || true
find . -type f -path "*shadcn*" -name "tailwind.css" 2>/dev/null || true
find . -type f -name "*.css" -print0 2>/dev/null | xargs -0 rg -n "data-horizontal:|data-vertical:|data-orientation|`@custom-variant`.*data-(horizontal|vertical)" 2>/dev/null || true

Repository: isntboxs/marvticle

Length of output: 132


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the shadcn/tailwind.css module file if it exists in node_modules
echo "---- candidate shadcn/tailwind.css files ----"
find node_modules -type f -path "*shadcn*" -name "tailwind.css" 2>/dev/null | head -n 50

echo "---- search for custom variants in any shadcn tailwind.css ----"
for f in $(find node_modules -type f -path "*shadcn*" -name "tailwind.css" 2>/dev/null); do
  echo "---- $f ----"
  rg -n "`@custom-variant`|data-horizontal|data-vertical|data-orientation" "$f" -S || true
done

# Also search node_modules for explicit custom variant declarations (limit to data-horizontal/data-vertical)
echo "---- repo-wide node_modules match for data-horizontal/data-vertical variants ----"
rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b|data-horizontal:|data-vertical:|data-orientation=\"(horizontal|vertical)\"" node_modules -S || true

Repository: isntboxs/marvticle

Length of output: 254


🌐 Web query:

Radix UI SeparatorPrimitive.Root data-orientation attribute orientation="horizontal|vertical"

💡 Result:

The Radix UI Separator primitive exposes a data-orientation attribute on the SeparatorPrimitive.Root component [1][2]. This data attribute automatically updates to reflect the current orientation prop [1], allowing for easy styling based on the separator's direction [3][4]. Key details regarding the data-orientation attribute: Attribute Values: The data-orientation attribute will hold a value of either "horizontal" or "vertical" [1][3]. Styling Usage: You can use this attribute in your CSS or styling solution (such as Tailwind CSS) to apply specific styles based on the orientation [3][4]. For example, in Tailwind CSS, you might use selectors like data-[orientation=horizontal]:h-px and data-[orientation=vertical]:w-px to adjust the thickness and dimensions of the separator accordingly [3]. Default Behavior: The orientation prop defaults to "horizontal" if not explicitly set [1][2]. Accessibility: The component automatically manages accessibility requirements and ensures the appropriate aria attributes are applied for the given orientation [1][3]. Example implementation using data-orientation for styling:.SeparatorRoot[data-orientation='horizontal'] { height: 1px; width: 100%; }.SeparatorRoot[data-orientation='vertical'] { height: 100%; width: 1px; }

Citations:


🏁 Script executed:

rg -n "`@custom-variant`\s+data-(horizontal|vertical)\b" node_modules -S || true

Repository: isntboxs/marvticle

Length of output: 44


Fix Tailwind orientation selectors for Radix Separator

SeparatorPrimitive.Root emits data-orientation="horizontal|vertical", but packages/ui/src/components/separator.tsx uses data-horizontal:* / data-vertical:* utilities. packages/ui/src/styles/globals.css only defines @custom-variant dark and no data-horizontal/data-vertical variants, so the size classes won’t apply.

Switch the classes to Tailwind’s data-selector form (or define matching @custom-variants), e.g. data-[orientation=horizontal]:h-px ... / data-[orientation=vertical]:w-px ....

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

In `@packages/ui/src/components/separator.tsx` around lines 18 - 21, The Tailwind
data selectors are wrong for SeparatorPrimitive.Root: replace the current
data-horizontal:* and data-vertical:* utilities in the className passed to the
Separator component with Tailwind data attribute selectors that match Radix's
data-orientation, e.g. use data-[orientation=horizontal]:h-px and
data-[orientation=horizontal]:w-full and data-[orientation=vertical]:w-px and
data-[orientation=vertical]:self-stretch (keep the cn(...) wrapper and preserve
the existing 'shrink-0 bg-border' and the passed className).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant