Skip to content

feat(web-ui): extract shared shadcn-style package for apps/landing and apps/guides#2075

Closed
andrew-bierman wants to merge 4 commits into
copilot/replicate-ui-style-in-landing-pagefrom
feat/shared-web-ui-package
Closed

feat(web-ui): extract shared shadcn-style package for apps/landing and apps/guides#2075
andrew-bierman wants to merge 4 commits into
copilot/replicate-ui-style-in-landing-pagefrom
feat/shared-web-ui-package

Conversation

@andrew-bierman
Copy link
Copy Markdown
Collaborator

@andrew-bierman andrew-bierman commented Apr 11, 2026

Summary

Introduces a new private workspace package @packrat/web-ui so apps/landing and apps/guides can share shadcn-style components, the cn() helper, globals.css tokens, and a tailwind preset instead of each maintaining their own duplicated copies.

Chained on #2052 (landing redesign) because that PR already brings landing and guides styles very close to identical. This PR formalizes that alignment into a shared package.

Structure follows shadcn's official monorepo guide.

Scope of this PR

Intentionally small: the package skeleton plus one proof-of-concept component (Button) migrated in apps/landing only. Everything else stays in each app for now — migration is the subject of future PRs.

What's in @packrat/web-ui

  • package.json — private, workspace:*, exports for ., ./components/*, ./lib/utils, ./styles/globals.css, ./tailwind/preset
  • tsconfig.json — matches the monorepo conventions used by @packrat/analytics
  • src/lib/utils.ts — canonical cn() helper (clsx + tailwind-merge)
  • src/components/button.tsx — first migrated component, imports cn from @packrat/web-ui/lib/utils
  • src/index.ts — barrel re-export
  • src/styles/globals.css — stub (future PRs move base layer + theme tokens here)
  • src/tailwind/preset.ts — stub Tailwind preset (future PRs move theme tokens here)

Root-level wiring

  • tsconfig.json gains @packrat/web-ui and @packrat/web-ui/* path aliases

Migration in apps/landing

  • Adds "@packrat/web-ui": "workspace:*" to apps/landing/package.json
  • Adds @packrat/web-ui path aliases to apps/landing/tsconfig.json (the app tsconfig does not extend root, so it needs its own entries)
  • Adds @packrat/web-ui to next.config.mjs transpilePackages so Next.js processes the package's TypeScript source
  • Adds ../../packages/web-ui/src/**/*.{ts,tsx} to tailwind.config.js content so classes used by the shared Button are emitted
  • apps/landing/components/ui/button.tsx is now a 6-line re-export stub (export { Button, buttonVariants, type ButtonProps } from '@packrat/web-ui') — all 11 existing callers of landing-app/components/ui/button continue to work unchanged

apps/guides is untouched in this PR.

Full migration plan (follow-up PRs)

Phase 1 — Migrate apps/landing components onto @packrat/web-ui (this PR: button.tsx only)

Still living in apps/landing/components/ui/ and need to move to packages/web-ui/src/components/ (re-exporting from the landing file or deleting the landing file and updating callers):

accordion.tsx             dropdown-menu.tsx      radio-group.tsx
alert-dialog.tsx          form.tsx               resizable.tsx
alert.tsx                 hover-card.tsx         scroll-area.tsx
animated-gradient-border.tsx   input-otp.tsx    select.tsx
animated-gradient-text.tsx     input.tsx        separator.tsx
aspect-ratio.tsx          label.tsx              sheet.tsx
avatar.tsx                menubar.tsx            sidebar.tsx
badge.tsx                 navigation-menu.tsx    skeleton.tsx
breadcrumb.tsx            pagination.tsx         slider.tsx
calendar.tsx              popover.tsx            sonner.tsx
card.tsx                  progress.tsx           switch.tsx
carousel.tsx                                     table.tsx
checkbox.tsx                                     tabs.tsx
collapsible.tsx                                  textarea.tsx
command.tsx                                      toast.tsx
context-menu.tsx                                 toaster.tsx
dialog.tsx                                       toggle-group.tsx
drawer.tsx                                       toggle.tsx
                                                 tooltip.tsx

Landing-only (likely stay in apps/landing/components/ui/ unless we decide to share):
animated-gradient-border.tsx, animated-gradient-text.tsx, device-mockup.tsx, feature-card.tsx, glass-card.tsx, gradient-background.tsx, gradient-border-card.tsx, gradient-text.tsx

Phase 2 — Migrate apps/guides onto @packrat/web-ui

Diff of apps/guides/components/ui/ vs apps/landing/components/ui/:

  • Guides-only: chart.tsx, use-mobile.tsx, use-toast.ts
  • Landing-only (likely stay landing-specific): the 8 gradient/mockup/glass components listed above

Once Phase 1 is done, migrating apps/guides should be mostly just adding the workspace dep, path alias, transpilePackages entry, tailwind content glob, and deleting its duplicated components/ui/* files.

Phase 3 — Deduplicate globals.css, tailwind.config, and lib/utils.ts

  • Move shared theme tokens / base layer from both apps into packages/web-ui/src/styles/globals.css and @import it from each app's app/globals.css. See the diff notes — most of the divergence is app-specific Apple-style helpers (landing) vs container + font-feature-settings (guides)
  • Populate packages/web-ui/src/tailwind/preset.ts with shared theme extend tokens so both apps/landing/tailwind.config.js and apps/guides/tailwind.config.ts can just presets: [require('@packrat/web-ui/tailwind/preset').default]
  • Delete the duplicated apps/*/lib/utils.ts cn() helpers and update callers to import from @packrat/web-ui/lib/utils (or just @packrat/web-ui)
  • Update each app's components.json to point at the shared globals.css per shadcn's guide

Test plan

  • bun install resolves the new package
  • bun run check-types clean
  • bun lint — my touched files produce zero diagnostics (the remaining ~170 warnings are pre-existing on the base branch)
  • apps/landing Next.js build compiles successfully (✓ Compiled successfully in 26.4s). The subsequent /500 prerender error is pre-existing on the base branch — confirmed by running bun run build on HEAD without my changes and reproducing the same TypeError: Cannot read properties of null (reading 'useRef') error at the same prerender step
  • Visual QA: apps/landing Button renders identically after the re-export swap
  • CI green

Summary by CodeRabbit

  • Refactor
    • Centralized button component styling and logic into a shared UI library for improved reusability across applications.
    • Restructured configuration files to integrate the new shared component library into the build and type-checking pipelines.

Introduces a new private workspace package that apps/landing and
apps/guides can share for shadcn-style components, the cn() helper,
globals.css tokens, and a tailwind preset. Structure follows shadcn's
official monorepo guide (https://ui.shadcn.com/docs/monorepo).

This commit only adds the skeleton — no apps are migrated yet:
- package.json (private, workspace:*, exports for components/lib/styles/tailwind)
- tsconfig.json extending the monorepo conventions used by @packrat/analytics
- src/lib/utils.ts canonical cn() helper (clsx + tailwind-merge)
- src/components/button.tsx — first proof-of-concept component
- src/index.ts barrel re-export
- src/styles/globals.css and src/tailwind/preset.ts stubs (future PRs will
  move the shared base layer + theme tokens here)
- Root tsconfig.json path aliases for @packrat/web-ui and @packrat/web-ui/*
Migrates apps/landing's Button component to re-export from the new
@packrat/web-ui workspace package so all existing
landing-app/components/ui/button callers keep working unchanged.

- Adds @packrat/web-ui as a workspace:* dependency
- Adds @packrat/web-ui and @packrat/web-ui/* path aliases to
  apps/landing/tsconfig.json (needed because the landing tsconfig does
  not extend the monorepo root)
- Registers @packrat/web-ui in next.config.mjs transpilePackages so
  Next.js transpiles the package's TypeScript source
- Adds ../../packages/web-ui/src glob to tailwind content so classes
  used by the shared Button are emitted
- apps/landing/components/ui/button.tsx is now a 6-line re-export stub
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed5a9b50-b18b-4a25-8494-a65821678e60

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A new shared UI package (@packrat/web-ui) is created with a reusable Button component, utilities, and configuration infrastructure. The landing app is refactored to import from this package instead of maintaining local definitions. TypeScript and Tailwind configurations are updated across the monorepo to support the new shared package structure.

Changes

Cohort / File(s) Summary
New Shared UI Package
packages/web-ui/package.json, packages/web-ui/tsconfig.json, packages/web-ui/src/components/button.tsx, packages/web-ui/src/index.ts, packages/web-ui/src/lib/utils.ts, packages/web-ui/src/styles/globals.css, packages/web-ui/src/tailwind/preset.ts
Created new workspace package with Button component featuring multiple variants and sizes, utility function cn() for class merging, Tailwind preset, and proper ES module + TypeScript configuration. Exports configured for component subpaths and utilities.
Landing App Component Refactoring
apps/landing/components/ui/button.tsx
Replaced local Button component implementation and buttonVariants definition with re-exports from @packrat/web-ui, reducing file from 48 to 6 lines.
Landing App Configuration Updates
apps/landing/package.json, apps/landing/next.config.mjs, apps/landing/tailwind.config.js, apps/landing/tsconfig.json
Added @packrat/web-ui dependency, configured Next.js transpilation for the new package, extended Tailwind content globs to scan shared package sources, and added TypeScript path aliases for package resolution.
Monorepo Configuration
tsconfig.json
Added root-level TypeScript path aliases mapping @packrat/web-ui to ./packages/web-ui/src for consistent module resolution across the monorepo.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A button hops to a shared warren,
No longer lonely in the landing app's glen,
Through TypeScript paths and Tailwind config,
Our UI components now play together—
One variant, many homes! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: extracting a shared shadcn-style web UI package for multiple apps.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/shared-web-ui-package

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown
Contributor

cloudflare-workers-and-pages Bot commented Apr 11, 2026

Deploying packrat-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5c04b2a
Status:🚫  Build failed.

View logs

@github-actions github-actions Bot added dependencies Pull requests that update a dependency file web labels Apr 11, 2026
@andrew-bierman
Copy link
Copy Markdown
Collaborator Author

@copilot fix failing build and ci checks

@andrew-bierman
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copilot AI and others added 2 commits April 11, 2026 18:49
Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/8e5068b2-ae2e-487b-92bb-5d3c2a4b84ed

Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
Agent-Logs-Url: https://github.com/PackRat-AI/PackRat/sessions/8e5068b2-ae2e-487b-92bb-5d3c2a4b84ed

Co-authored-by: andrew-bierman <94939237+andrew-bierman@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 11, 2026

@copilot fix failing build and ci checks

Fixed in ba33f6b. The CI was failing because the PR merge commit included files from the base branch that had 3 biome errors:

  1. apps/expo/features/packs/components/PackCard.tsx — two Alert.alert() calls exceeded 100-char line width (formatting error)
  2. apps/expo/features/trips/screens/TripDetailScreen.tsxuseMemo at line 35 had overly-specific dependencies (trip?.location?.latitude, trip?.location?.longitude) when the callback captures trip?.location directly; fixed by using [trip?.location]

Biome check now passes locally with 0 errors (165 pre-existing warnings).

@andrew-bierman andrew-bierman marked this pull request as ready for review April 11, 2026 19:05
Copilot AI review requested due to automatic review settings April 11, 2026 19:05
@andrew-bierman andrew-bierman deleted the branch copilot/replicate-ui-style-in-landing-page April 11, 2026 19:08
@andrew-bierman
Copy link
Copy Markdown
Collaborator Author

@claude re open

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new shared workspace package @packrat/web-ui to begin deduplicating shadcn-style web UI between apps/landing and apps/guides, and includes a small landing-only proof-of-concept migration (Button). This PR also contains unrelated Expo app changes (trips location store behavior, map remount fix, and pack duplication UI).

Changes:

  • Add packages/web-ui workspace package with cn(), a shared Button, and stubs for globals.css + Tailwind preset; wire up TS path aliases.
  • Migrate apps/landing’s Button to re-export from @packrat/web-ui, and update Next/Tailwind configs to consume the package.
  • Update Expo trips/pack screens: stabilize MapView remounting, adjust trip location store behavior, and add pack duplication capability + UI.

Reviewed changes

Copilot reviewed 28 out of 30 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tsconfig.json Adds root TS path aliases for @packrat/web-ui.
scripts/lint/no-circular-deps.ts Refactors trailing-glob regex into a constant.
scripts/check-all.ts Refactors regex patterns and string formatting in summary extraction.
packages/web-ui/package.json Defines the new private @packrat/web-ui package exports and deps.
packages/web-ui/tsconfig.json Adds strict TS config + local path aliases for the package.
packages/web-ui/src/index.ts Barrel exports for Button and cn().
packages/web-ui/src/lib/utils.ts Introduces canonical cn() helper (clsx + tailwind-merge).
packages/web-ui/src/components/button.tsx Adds shared shadcn-style Button implementation.
packages/web-ui/src/styles/globals.css Adds stub shared globals stylesheet (future tokens).
packages/web-ui/src/tailwind/preset.ts Adds stub Tailwind preset (future shared theme).
bun.lock Updates lockfile to include the new workspace package and dependency resolutions.
apps/landing/package.json Adds @packrat/web-ui workspace dependency.
apps/landing/tsconfig.json Adds app-level TS path aliases for @packrat/web-ui.
apps/landing/next.config.mjs Adds @packrat/web-ui to transpilePackages.
apps/landing/tailwind.config.js Adds packages/web-ui/src to Tailwind content globs.
apps/landing/components/ui/button.tsx Replaces local implementation with re-export stub from @packrat/web-ui.
apps/landing/components/site-footer.tsx Minor layout tweak (removes lg:ml-auto).
apps/landing/components/sections/testimonials.tsx Adjusts quote icon positioning + overflow clipping.
apps/landing/components/sections/landing-hero.tsx Centers hero content on mobile; adjusts alignment utilities.
apps/landing/components/sections/how-it-works.tsx Updates connector line to show only on lg 3-col layout.
apps/landing/components/sections/feature-section.tsx Removes redundant wrapper to avoid double card styling.
apps/landing/components/sections/download.tsx Removes client-side scroll handler; uses external links with safe rel attrs; centers content on mobile.
apps/expo/features/trips/store/tripLocationStore.ts Makes location name optional and memoizes setter with useCallback.
apps/expo/features/trips/screens/TripDetailScreen.tsx Adds stable key to MapView to force remount when coordinates change.
apps/expo/features/trips/components/TripForm.tsx Adds effect to seed/clear location store based on trip ID.
apps/expo/features/packs/screens/PackListScreen.tsx Passes showDuplicateButton into PackCard based on selected filter.
apps/expo/features/packs/hooks/useDuplicatePack.ts Adds duplication hook that fetches a pack and clones it locally.
apps/expo/features/packs/hooks/useCreatePackFromPack.ts Adds helper hook to clone a pack + its items into local stores.
apps/expo/features/packs/hooks/index.ts Re-exports the new hooks.
apps/expo/features/packs/components/PackCard.tsx Adds duplicate button UI (and loading state) for non-owned packs; refactors tags row layout.

Comment on lines +67 to +71
// Set location from trip, or null if trip has no location
setLocation(trip?.location ?? null);

// Cleanup: clear location when component unmounts
return () => {
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

The new effect seeds the shared location store from trip?.location, but the existing effect later in this component (useEffect(() => { tripLocationStore.set(null) ... }, [])) will run after this one on mount and immediately clear the store back to null. That makes the seeded location unavailable when editing an existing trip.

Consider removing the older “reset store on mount” effect, or folding the stale-state reset into this effect (e.g., only clear on mount when !trip, or clear first and then seed), so the final mounted state matches trip?.location when editing.

Suggested change
// Set location from trip, or null if trip has no location
setLocation(trip?.location ?? null);
// Cleanup: clear location when component unmounts
return () => {
const nextLocation = trip?.location ?? null;
// Defer the seed so the final mounted store state matches the current
// trip, even if another mount-only effect in this component clears the
// shared location store during the same commit.
const timeoutId = setTimeout(() => {
setLocation(nextLocation);
}, 0);
// Cleanup: cancel pending seed and clear location when component unmounts
return () => {
clearTimeout(timeoutId);

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +16
name: packData.name || `${sourcePack.name} (Copy)`,
description: packData.description || sourcePack.description,
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

packData.name / packData.description are merged using ||, which will treat empty strings as “not provided” and fall back to the source pack values. If callers ever intentionally pass an empty string (e.g., to clear description), this will be impossible.

Use nullish coalescing (??) for string fields so only undefined/null trigger the fallback.

Suggested change
name: packData.name || `${sourcePack.name} (Copy)`,
description: packData.description || sourcePack.description,
name: packData.name ?? `${sourcePack.name} (Copy)`,
description: packData.description ?? sourcePack.description,

Copilot uses AI. Check for mistakes.
"./components/*": "./src/components/*.tsx",
"./lib/utils": "./src/lib/utils.ts",
"./styles/globals.css": "./src/styles/globals.css",
"./tailwind/preset": "./src/tailwind/preset.ts"
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

@packrat/web-ui exports the Tailwind preset as a TypeScript source file (./tailwind/preset -> ./src/tailwind/preset.ts). Tailwind configs are often executed by Node (and apps/landing currently uses a CJS tailwind.config.js), so consuming this via require('@packrat/web-ui/tailwind/preset') will fail unless you add a TS runtime loader or ship compiled JS.

To avoid future integration issues, consider exporting a JS/ESM file for the preset (or add a build step that emits JS + d.ts and point exports at the emitted files).

Suggested change
"./tailwind/preset": "./src/tailwind/preset.ts"
"./tailwind/preset": {
"types": "./src/tailwind/preset.ts",
"import": "./dist/tailwind/preset.js",
"default": "./dist/tailwind/preset.js"
}

Copilot uses AI. Check for mistakes.
Comment on lines +26 to +27
// @ts-ignore: Safe because Legend-State uses Proxy
packsStore[newPackId].set(newPack);
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

This hook uses // @ts-ignore and direct index access on Legend-State stores (packsStore[newPackId].set(...), packItemsStore[newItemId].set(...)). Elsewhere in the packs feature the established pattern is to use the obs(store, id) helper (e.g. apps/expo/features/packs/hooks/useCreatePack.ts:21, useCreatePackItem.ts:31) which preserves typing and avoids suppressing errors.

Consider switching these writes to obs(packsStore, newPackId).set(...) / obs(packItemsStore, newItemId).set(...) and dropping the @ts-ignore comments.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants