diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..26d664e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,159 @@ +name: CI + +on: + pull_request: + push: + branches: [ main ] + +jobs: + scan_ruby: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Scan for common Rails security vulnerabilities using static analysis + run: bin/brakeman --no-pager + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Lint code for consistent style + run: bin/rubocop -f github + + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install JS dependencies + run: npm ci + + - name: Type check + run: npm run check + + test: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install JS dependencies + run: npm ci + + - name: Run tests + env: + RAILS_ENV: test + DATABASE_USER: postgres + DATABASE_PASSWORD: postgres + DATABASE_HOST: localhost + DATABASE_PORT: 5432 + run: bin/rails db:test:prepare test + + - name: Keep screenshots from failed tests + uses: actions/upload-artifact@v4 + if: failure() + with: + name: screenshots + path: ${{ github.workspace }}/tmp/screenshots + if-no-files-found: ignore + + test_system: + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:14 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - name: Install Google Chrome + uses: browser-actions/setup-chrome@v1 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: .ruby-version + bundler-cache: true + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install JS dependencies + run: npm ci + + - name: Run system tests + env: + RAILS_ENV: test + DATABASE_USER: postgres + DATABASE_PASSWORD: postgres + DATABASE_HOST: localhost + DATABASE_PORT: 5432 + run: bin/rails db:test:prepare test:system + + - name: Keep screenshots from failed system tests + uses: actions/upload-artifact@v4 + if: failure() + with: + name: system-test-screenshots + path: ${{ github.workspace }}/tmp/screenshots + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore index 8a5e7a5..fbf85af 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ !/tmp/pids/ !/tmp/pids/.keep -# Ignore storage (uploaded files in development). +# Ignore Active Storage uploads. /storage/* !/storage/.keep /tmp/storage/* diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..1d9b783 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.12.0 diff --git a/.ruby-version b/.ruby-version index 944880f..9c25013 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.0 +3.3.6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 48c9169..50dccd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ Versions are numbered using the release date in `YYYY.MM.DD` format. +## 2026.5.9 + +- Switched the database from SQLite back to PostgreSQL. Single database at `build_new_` is shared by Active Record and the Solid trifecta (Queue, Cache, Cable). Connection is configurable via `DATABASE_URL` or the `DATABASE_USER` / `DATABASE_PASSWORD` / `DATABASE_HOST` / `DATABASE_PORT` env vars. +- Added `admin` boolean column to users (default `false`) and an `/admin` namespace gated by `Admin::BaseController` (admins only). Added admin Users index + show pages and a Shield-icon Admin link in the user menu when `current_user.admin` is true. + +## 2026.5.8 + +Reset to a true blank slate. + +- Replaced PostgreSQL with SQLite. Single database at `storage/.sqlite3` is shared by Active Record and the Solid trifecta (Queue, Cache, Cable). +- Removed Tailwind CSS, shadcn/ui, `tw-animate-css`, Radix primitives, `lucide-react`, `class-variance-authority`, `clsx`, `tailwind-merge`, `cn()` utility, and `components.json`. +- Removed the authenticated `AppShell` (sidebar + header dropdown) and the `AuthCard` wrapper. +- Removed the system-preference dark-mode bootstrap and CSS theme variables. +- All pages are now plain unstyled HTML — pick a UI approach per app. + ## 2026.4.27 Initial release. diff --git a/CLAUDE.md b/CLAUDE.md index ffb5ab0..c2d4e18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,9 +4,11 @@ Guidance for Claude Code (claude.ai/code) working in this repo. ## Tech Stack -Rails 8 + React 19 + PostgreSQL, bridged by **Inertia.js** (no separate API layer). TypeScript, Tailwind CSS 4, shadcn/ui (new-york), Vite 7, Propshaft. Ruby 3.2.0. +Rails 8 + React 19 + PostgreSQL, bridged by **Inertia.js** (no separate API layer). TypeScript, Vite 7, Propshaft. Ruby 3.3.6. -Background jobs, caching, and WebSockets use the Rails 8 "Solid" trifecta (Solid Queue, Solid Cache, Solid Cable), all database-backed. **All three share the single primary PostgreSQL database** — there are no separate cache/cable databases, no `db/cache_schema.rb` or `db/cable_schema.rb`, and `config/cache.yml` / `config/cable.yml` have no separate connection blocks. +**Tailwind CSS v4** is wired up via `@tailwindcss/vite`. The app has a complete design system (tokens, primitives, dark-mode theming, the `cn()` utility, shared components under `app/frontend/components/`) — see the "Design system" section below and the live reference at `/admin/design-system`. + +Background jobs, caching, and WebSockets use the Rails 8 "Solid" trifecta (Solid Queue, Solid Cache, Solid Cable), all database-backed. **All four share the single PostgreSQL database** (`build_new_` by default) — there are no separate cache/cable/queue databases, no `db/cache_schema.rb` / `db/cable_schema.rb` / `db/queue_schema.rb`, and `config/cache.yml` / `config/cable.yml` / `config/queue.yml` have no separate connection blocks. Override the connection via `DATABASE_URL` or the `DATABASE_USER` / `DATABASE_PASSWORD` / `DATABASE_HOST` / `DATABASE_PORT` env vars (see `config/database.yml`). ## Commands @@ -35,47 +37,27 @@ The page name resolves to a React component in `app/javascript/pages/` via `app/ ### Frontend directory layout - **`app/javascript/`** — Vite source: `entrypoints/`, page components in `pages/` -- **`app/frontend/`** — Shared React code: shadcn/ui in `components/ui/`, app shell in `components/app-shell.tsx`, auth card in `components/auth-card.tsx`, utilities in `lib/`, shared Inertia types in `types/inertia.ts` +- **`app/frontend/`** — Shared React code: `types/inertia.ts`, the design system under `components/design-system/` and `components/ui/`, helpers under `lib/`, and the design-system stylesheet under `styles/`. -The `@` path alias resolves to `app/frontend/` in both Vite and TypeScript configs. Import shared code as `@/components/ui/button`, `@/lib/utils`, `@/types/inertia`. +The `@` path alias resolves to `app/frontend/` in both Vite and TypeScript configs. Import shared code as `@/types/inertia`, `@/components/ui/button`, `@/lib/utils`, etc. ### Adding a new page 1. Add a route in `config/routes.rb` 2. Controller action calls `render inertia: "PageName", props: { ... }` 3. Create `app/javascript/pages/PageName.tsx` -4. Wrap authenticated pages in `` from `@/components/app-shell` -5. Set ``, ``, ``, and `` on the page (see "Page metadata" below) — required for every page, no exceptions -6. If the page is **publicly viewable** (no `require_authentication`), also: +4. Set ``, ``, ``, and `` on the page (see "Page metadata" below) — required for every page, no exceptions +5. If the page is **publicly viewable** (no `require_authentication`), also: - Add it to `config/sitemap.rb` so crawlers discover it - Add it to `public/llms.txt` under the right section - Make sure it is not blocked in `public/robots.txt` -### Auth - -Generated with `bin/rails g authentication` and customized for Inertia: - -- Routes: `/login`, `/signup`, `/logout`, `/passwords/new`, `/passwords/:token/edit` -- `User` fields: `email`, `password_digest`, `timezone` -- `SessionsController`, `RegistrationsController`, `PasswordsController` render Inertia pages for `new`/`edit` and redirect on mutations -- `ApplicationController` uses `inertia_share` to expose `current_user`, `flash`, and `errors` on every page -- `Current.user` (`app/models/current.rb`) delegates to `session.user` -- Signup captures the browser's IANA timezone via `Intl.DateTimeFormat().resolvedOptions().timeZone` and stores it on the user - -### Mail - -`config/environments/development.rb` sets `config.action_mailer.delivery_method = :letter_opener`. The `letter_opener_web` engine is mounted at `/letter_opener` in development only (see `config/routes.rb`). Production mail is not configured — wire up SMTP in `config/environments/production.rb`. - -### Dark mode - -System preference, via an inline script in `app/views/layouts/application.html.erb` that toggles `.dark` on `` based on `prefers-color-scheme` before first paint. CSS variables in `app/javascript/entrypoints/application.css` define both themes. - ### Key files - `app/javascript/entrypoints/inertia.ts` — React mount point, page resolution -- `app/javascript/entrypoints/ssr.tsx` — SSR mount point (mirrors `inertia.ts` but renders to string) -- `app/javascript/entrypoints/application.css` — Tailwind 4 theme (light/dark CSS variables) -- `app/views/layouts/application.html.erb` — Vite client, Inertia entrypoint, dark-mode bootstrap, `inertia_ssr_head` +- `app/javascript/ssr/ssr.tsx` — SSR mount point (mirrors `inertia.ts` but renders to string); auto-detected by vite-plugin-ruby +- `app/javascript/entrypoints/application.css` — Tailwind import, `@source` directive, and the design-system stylesheet import +- `app/views/layouts/application.html.erb` — Vite client, Inertia entrypoint, `inertia_ssr_head` - `app/controllers/application_controller.rb` — `inertia_share` for shared props - `app/controllers/concerns/authentication.rb` — session helpers, `require_authentication` - `config/initializers/inertia_rails.rb` — Inertia config (encrypted history, auto-included errors hash, SSR) @@ -83,7 +65,6 @@ System preference, via an inline script in `app/views/layouts/application.html.e - `config/sitemap.rb` — sitemap_generator config; lists every public URL - `public/robots.txt` — crawler allow/deny rules + sitemap pointer - `public/llms.txt` — curated, plain-text site map for LLM crawlers -- `components.json` — shadcn/ui config ## Inertia controller response rules (common LLM footgun) @@ -123,34 +104,26 @@ end Inertia SSR is wired up so search engines and LLM crawlers (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, etc.) receive fully rendered HTML instead of an empty `
` populated only by client-side JavaScript. Without SSR, public pages are effectively invisible to non-JS crawlers. -**How it works.** When SSR is enabled, the Rails request handler POSTs the page name + props to a long-running Node process (default `http://localhost:13714`). That process runs `app/javascript/entrypoints/ssr.tsx`, renders the React tree with `ReactDOMServer.renderToString`, and returns the HTML + `` tags. Rails inlines them via `<%= inertia_ssr_head %>` and the rendered markup in `app/views/layouts/application.html.erb`. The client-side bundle then hydrates on top of that markup. +**How it works.** When SSR is enabled, the Rails request handler POSTs the page name + props to a long-running Node process (default `http://localhost:13714`). That process runs the SSR bundle built from `app/javascript/ssr/ssr.tsx`, renders the React tree with `ReactDOMServer.renderToString`, and returns the HTML + `` tags. Rails inlines them via `<%= inertia_ssr_head %>` and the rendered markup in `app/views/layouts/application.html.erb`. The client-side bundle then hydrates on top of that markup. **Configuration.** - `config/initializers/inertia_rails.rb` — `ssr_enabled` is on in production by default, off in development. Override with `INERTIA_SSR=1` (force on) or `INERTIA_SSR=0` (force off). - `vite.config.ts` — `ssr: { noExternal: true }` bundles all dependencies into the SSR output so the Node process boots without needing `node_modules` resolution at runtime. -- `package.json` — `npm run build:ssr` produces `public/vite-ssr/ssr.js`; `npm run ssr` runs it. - -**Local SSR testing (recommended after touching pages, the layout, or shared components).** +- `bin/vite build --ssr` produces `public/vite-ssr/ssr.js` (vite-plugin-ruby's default SSR output path); `bin/vite ssr` runs it. -1. `npm run build:ssr` -2. In a second terminal: `npm run ssr` -3. Start Rails with `INERTIA_SSR=1 bin/dev` -4. Load a page and view source — the `
` should contain real markup, not an empty container. +**Local SSR testing.** Run `bin/dev-ssr` instead of `bin/dev`. It builds the SSR bundle, sets `INERTIA_SSR=1`, and runs Rails + Vite + Solid Queue + an SSR build watcher + the Node SSR server together via `Procfile.ssr`. View source on a page — the `
` should contain real markup, not an empty container. -A commented `ssr_build` + `ssr` block in `Procfile.dev` automates steps 1–2 if uncommented. +**SSR smoke test.** `bin/rails test test/integration/ssr_smoke_test.rb` builds the SSR bundle, boots the Node server, hits `/render`, and asserts non-empty markup. It also runs as part of `bin/rails test`. Use it to catch breakage from changes to entrypoints, shared providers, or anything imported during SSR. -**Production checklist.** The deploy pipeline must: - -1. Run `npm run build` (client bundle) **and** `npm run build:ssr` (SSR bundle). -2. Start the Node SSR process (`node public/vite-ssr/ssr.js`) alongside Rails — typically as a separate Procfile entry, container sidecar, or systemd unit. If the SSR process is unreachable, Inertia falls back to an empty `
` and crawlers see nothing. +Production deployment is host-specific (Render, Fly, Heroku, container, bare metal, etc.) and not prescribed here. The Inertia Rails renderer silently falls back to a client-only render if the SSR Node process is unreachable, so the smoke test is your guardrail — keep it green and the SSR pipeline works. **Keeping SSR working.** - Anything imported by a page component runs in Node during SSR. **Never reference `window`, `document`, `localStorage`, or other browser-only globals at module top-level or during render.** Guard with `typeof window !== "undefined"` or move the access into a `useEffect`. - Don't add code paths in `inertia.ts` (client) without mirroring them in `ssr.tsx` if they affect rendered output (e.g. shared providers, default layouts). The two entrypoints must produce the same component tree. - Avoid randomness, `Date.now()`, and other non-deterministic values during render — they cause hydration mismatches. -- After adding heavy native deps, re-run `npm run build:ssr` locally; if it fails because of an ESM/CJS issue, add the offending package to `ssr.noExternal` exceptions in `vite.config.ts` (or leave `noExternal: true` and pin the package version that works). +- After adding heavy native deps, re-run `bin/vite build --ssr` locally; if it fails because of an ESM/CJS issue, add the offending package to `ssr.noExternal` exceptions in `vite.config.ts` (or leave `noExternal: true` and pin the package version that works). ## Crawler discovery: sitemap.xml, robots.txt, llms.txt @@ -203,7 +176,37 @@ export default function Pricing() { ## Conventions - Ruby: `rubocop-rails-omakase` style, `frozen_string_literal: true` -- Tailwind 4 `@theme inline` with CSS custom properties for theming +- Tailwind CSS v4 + a complete design system (tokens, primitives, dark mode) — see the "Design system" section below and `/admin/design-system` - `ApplicationController` restricts to modern browsers - Inertia shared props: `current_user`, `flash`, `errors` on every page (see `@/types/inertia`) -- PostgreSQL is required locally for development and tests +- PostgreSQL (database `build_new_` by default) is the only database — Active Record + Solid Queue/Cache/Cable all share it + + +## Design system + +This codebase has a design system documented at [`/admin/design-system`](/admin/design-system). The page previews and explains every primitive — colors, typography, structure, base styles, and elements — and shows the exact markup to use. + +When implementing UI: + +1. **Always check the design system first.** Before writing any frontend markup or styles, refer to `/admin/design-system` and the components under `components/ui/` and `components/design-system/sections/`. Use the existing tokens (`bg-page`, `bg-surface`, `text-ink-body`, etc.) and the existing primitives (`
+ + {/* Select */} +
+ + +
+ + {/* Radio group */} +
+ Plan + + + + + +
+ + {/* Checkbox */} + + + {/* Rich text (milkdown) */} +
+ + +
+ + +`; + +export function FormsSection() { + return ( + + Forms compose <Input>, <Select>, + <Checkbox>, <Radio>,{" "} + <RichTextField> (milkdown), native HTML labels, + helper text, and <Button>. Vertical spacing between + fields uses space-y-4; spacing inside a field uses{" "} + space-y-2. + + } + whenToUse={ +
    +
  • All data-entry surfaces.
  • +
  • Wrap fields with their own <label> for accessibility.
  • +
  • Use <fieldset> + <legend> around radio groups.
  • +
+ } + whenNotToUse={ +
    +
  • Inline filters in toolbars — use compact controls instead.
  • +
  • Single-button calls to action — those don't need a form wrapper unless they POST.
  • +
+ } + preview={ +
e.preventDefault()} + > +
+ + +

+ We'll only use this for account notifications. +

+
+ +
+ + +
+ +
+ + Plan + + + + + + +
+ + + +
+ + +
+ + +
+ } + code={code} + options={ +
    +
  • Use HTML <label htmlFor> with the field's id for accessibility.
  • +
  • Helper text uses text-xs text-ink-muted directly under the field.
  • +
  • Error states: render a text-xs text-signal message in the same slot as helper text and add aria-invalid to the field.
  • +
  • Radio groups: wrap in <fieldset> + <legend> and share a name across all <Radio> inputs.
  • +
  • Selects: use a disabled empty <option> as a placeholder when no default makes sense.
  • +
  • Rich text: <RichTextField> wraps milkdown's Crepe — emits markdown via onChange. Requires @milkdown/crepe.
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/elements/IconographySection.tsx b/app/frontend/components/design-system/sections/elements/IconographySection.tsx new file mode 100644 index 0000000..d4e08c3 --- /dev/null +++ b/app/frontend/components/design-system/sections/elements/IconographySection.tsx @@ -0,0 +1,85 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; +import { + Activity, + ArrowRight, + Bell, + Check, + ChevronRight, + Folder, + Plus, + Search, + Settings, + Trash2, + User, + X, +} from "lucide-react"; + +const code = `import { Plus } from "lucide-react"; + +`; + +const ICONS = [ + { Icon: Activity, name: "Activity" }, + { Icon: ArrowRight, name: "ArrowRight" }, + { Icon: Bell, name: "Bell" }, + { Icon: Check, name: "Check" }, + { Icon: ChevronRight, name: "ChevronRight" }, + { Icon: Folder, name: "Folder" }, + { Icon: Plus, name: "Plus" }, + { Icon: Search, name: "Search" }, + { Icon: Settings, name: "Settings" }, + { Icon: Trash2, name: "Trash2" }, + { Icon: User, name: "User" }, + { Icon: X, name: "X" }, +]; + +export function IconographySection() { + return ( + + Icons come from lucide-react. Default size is{" "} + h-4 w-4 for inline use; h-5 w-5 in + buttons; h-6 w-6 for standalone visual anchors. + + } + whenToUse={ +
    +
  • Reinforce a button's label or status.
  • +
  • Visually anchor empty states and section headers.
  • +
+ } + whenNotToUse={ +
    +
  • Decoratively, with no semantic value.
  • +
  • Replacing a label entirely (icon-only buttons need an aria-label).
  • +
+ } + preview={ +
+ {ICONS.map(({ Icon, name }) => ( +
+ + + {name} + +
+ ))} +
+ } + code={code} + options={ +
    +
  • Pick from the full lucide set: https://lucide.dev/icons
  • +
  • Use text-ink-muted for icons that recede; text-accent when emphasizing.
  • +
  • Always pair icon-only buttons with aria-label.
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/elements/ListingsSection.tsx b/app/frontend/components/design-system/sections/elements/ListingsSection.tsx new file mode 100644 index 0000000..2e4d928 --- /dev/null +++ b/app/frontend/components/design-system/sections/elements/ListingsSection.tsx @@ -0,0 +1,85 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; +import { Badge } from "@/components/ui/badge"; +import { ChevronRight, Folder } from "lucide-react"; + +const code = ``; + +const ITEMS = [ + { id: 1, title: "Onboarding redesign", subtitle: "Updated 2h ago · Marie", status: "Active" }, + { id: 2, title: "Q2 marketing rollout", subtitle: "Updated yesterday · Jordan", status: "Active" }, + { id: 3, title: "API v2 migration", subtitle: "Updated 3d ago · Priya", status: "Draft" }, + { id: 4, title: "Mobile launch checklist", subtitle: "Archived last week", status: "Archived" }, +]; + +export function ListingsSection() { + return ( + + A vertical list of selectable rows — the workhorse of dashboards + and resource indexes. Each row composes an icon, a title, supporting + metadata, an optional Badge, and a chevron affordance. + + } + whenToUse={ +
    +
  • Lists of resources (projects, files, members).
  • +
  • Search results, recent activity feeds.
  • +
+ } + whenNotToUse={ +
    +
  • Dense, multi-column tabular data — use a table.
  • +
  • Inline option lists in dropdowns — use a menu primitive.
  • +
+ } + preview={ +
    + {ITEMS.map((item) => ( +
  • +
    + +
    +
    + {item.title} +
    +
    + {item.subtitle} +
    +
    + + {item.status} + + +
    +
  • + ))} +
+ } + code={code} + options={ +
    +
  • Wrap rows in <a> for navigation, or <button> for in-page selection.
  • +
  • Drop the chevron when the row is non-navigable.
  • +
  • Use truncate on title and subtitle to prevent overflow.
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/elements/ModalSection.tsx b/app/frontend/components/design-system/sections/elements/ModalSection.tsx new file mode 100644 index 0000000..7a66707 --- /dev/null +++ b/app/frontend/components/design-system/sections/elements/ModalSection.tsx @@ -0,0 +1,115 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; + +const code = `import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; + + + + + + + + Are you sure? + This action cannot be undone. + + + + + + +`; + +export function ModalSection() { + return ( + + Built on Radix Dialog. Three sizes (sm,{" "} + md, lg). Always uses an overlay and a + close button. Focus is trapped while open. + + } + whenToUse={ +
    +
  • Confirmation prompts (delete, discard, etc.).
  • +
  • Focused tasks that block the underlying view.
  • +
+ } + whenNotToUse={ +
    +
  • Casual notifications — use a toast.
  • +
  • Long, scroll-heavy forms — use a dedicated page.
  • +
  • Stacked over another modal — flatten the flow instead.
  • +
+ } + preview={ +
+ + + + + + + Delete project? + + This permanently removes the project and all of its data. + This action cannot be undone. + + + + + + + + + + + + + + + + Larger dialog + + Use the size="lg" variant when you need more + horizontal room for content. + + + + + + + +
+ } + code={code} + options={ +
    +
  • size on <DialogContent>: sm | md (default) | lg
  • +
  • Always include <DialogTitle> — required for screen readers.
  • +
  • Use <DialogDescription> for the supporting copy line.
  • +
  • Buttons live inside <DialogFooter>; right-aligned on desktop, stacked on mobile.
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/structure/BodyContentSection.tsx b/app/frontend/components/design-system/sections/structure/BodyContentSection.tsx new file mode 100644 index 0000000..036f6f7 --- /dev/null +++ b/app/frontend/components/design-system/sections/structure/BodyContentSection.tsx @@ -0,0 +1,83 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; + +const code = `
+

Section heading

+

Lead paragraph introducing the section.

+

Continued prose with consistent vertical rhythm.

+

Sub-section

+

Each h2/h3/h4 inside .body-content gets pt-4 unless it's the first child.

+
    +
  • List items use the base styles defined globally.
  • +
  • No extra utility classes needed.
  • +
+
And blockquotes look the same wherever they appear.
+
`; + +export function BodyContentSection() { + return ( + + Wrap any long-form prose (articles, marketing copy, doc pages) in + .body-content to get consistent vertical rhythm + (space-y-6) and breathing room above sub-headings. + We do not use Tailwind's typography plugin. + + } + whenToUse={ +
    +
  • Articles, blog posts, documentation pages.
  • +
  • Anywhere you have a sequence of paragraphs and headings.
  • +
+ } + whenNotToUse={ +
    +
  • UI chrome — buttons, forms, navs, cards.
  • +
  • Tightly-spaced layouts where you control gaps explicitly.
  • +
+ } + preview={ +
+

Section heading

+

+ Lead paragraph introducing the section. The body font and base + type sizes come from the global stylesheet. +

+

+ A second paragraph shows the consistent vertical rhythm given + by .body-content > * + * with{" "} + margin-top: 1.5rem. +

+

Sub-section

+

+ Each h2, h3, or h4 inside + .body-content gets pt-4 unless it's + the first child. +

+
    +
  • Lists inherit base styles globally.
  • +
  • No extra Tailwind utility classes are needed.
  • +
+
+ And blockquotes look the same wherever they appear. +
+
+ } + code={code} + options={ +
    +
  • + .body-content applies vertical spacing only — no + font or color overrides. Type comes from base styles. +
  • +
  • + Custom widths are fine: pair with{" "} + max-w-prose or your own constraint. +
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/structure/FootersSection.tsx b/app/frontend/components/design-system/sections/structure/FootersSection.tsx new file mode 100644 index 0000000..4d84cb0 --- /dev/null +++ b/app/frontend/components/design-system/sections/structure/FootersSection.tsx @@ -0,0 +1,50 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; + +const code = ``; + +export function FootersSection() { + return ( + + The bottom edge of a shell. Quiet, low-contrast. Holds the + copyright, legal links, and (sparingly) secondary navigation. + + } + whenToUse={ +
    +
  • Public-facing or marketing pages.
  • +
  • Authenticated screens that have natural scroll endings.
  • +
+ } + whenNotToUse={ +
    +
  • Dense app screens (dashboards, editors) — they don't need a footer.
  • +
  • Modals.
  • +
+ } + preview={ +
+
+ © 2026 Acme + +
+
+ } + code={code} + /> + ); +} diff --git a/app/frontend/components/design-system/sections/structure/MainNavSection.tsx b/app/frontend/components/design-system/sections/structure/MainNavSection.tsx new file mode 100644 index 0000000..68a5403 --- /dev/null +++ b/app/frontend/components/design-system/sections/structure/MainNavSection.tsx @@ -0,0 +1,604 @@ +import * as React from "react"; +import { SectionShell } from "@/components/design-system/SectionShell"; +import { cn } from "@/lib/utils"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { ThemeToggle } from "@/components/ui/theme-toggle"; +import { + ChevronsLeft, + ChevronsRight, + Folder, + Home, + LogOut, + Menu, + Settings, + User, + Users, + X, +} from "lucide-react"; + +const STORAGE_KEY = "bm-ds-main-nav-open"; + +const code = `// app/components/MainNav.tsx +"use client"; +import * as React from "react"; +import { + DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { ThemeToggle } from "@/components/ui/theme-toggle"; +import { + ChevronsLeft, ChevronsRight, Folder, Home, LogOut, Menu, + Settings, User, Users, X, +} from "lucide-react"; +import { cn } from "@/lib/utils"; + +const STORAGE_KEY = "main-nav-open"; + +function useMainNavOpen() { + // SSR-safe: default to true, then hydrate from localStorage on mount. + const [open, setOpen] = React.useState(true); + React.useEffect(() => { + const stored = window.localStorage.getItem(STORAGE_KEY); + if (stored !== null) setOpen(stored === "true"); + }, []); + React.useEffect(() => { + window.localStorage.setItem(STORAGE_KEY, String(open)); + }, [open]); + return [open, setOpen] as const; +} + +export function MainNav() { + const [open, setOpen] = useMainNavOpen(); + const [mobileOpen, setMobileOpen] = React.useState(false); + + return ( + <> + {/* Desktop rail — hidden below lg */} + + + {/* Mobile drawer — slides in from the left, dismisses on overlay click */} + {mobileOpen && ( +
+
setMobileOpen(false)} + aria-hidden + /> + +
+ )} + + {/* Mobile hamburger — fixed to the top-right corner of the viewport */} + + + ); +} + +function RailBody({ open, onToggle }: { open: boolean; onToggle: () => void }) { + return ( + <> + {/* Brand row — when expanded, the collapse chevron sits next to the brand */} +
+ + + A + + {open && ( + + Acme + + )} + + {open && ( + + )} +
+ + + +
+ +
+ + {/* Expand chevron — only renders when collapsed, sits below the user menu */} + {!open && ( +
+ +
+ )} + + ); +} + +function RailNav({ open, onClose }: { open: boolean; onClose?: () => void }) { + return ( + + ); +} + +function NavItem({ href, icon: Icon, label, active, open, onClick }: { + href: string; + icon: React.ComponentType<{ className?: string }>; + label: string; + active?: boolean; + open: boolean; + onClick?: () => void; +}) { + return ( +
+ + + {open && {label}} + + {!open && ( + // Floating label — pops outside the rail on hover, doesn't expand it + + {label} + + )} +
+ ); +} + +function UserMenu({ open }: { open: boolean }) { + const email = "you@example.com"; + const initial = email.charAt(0).toUpperCase(); + return ( + + + + + + + Signed in as + {email} + + + Profile + Settings + +
+ +
+ + Sign out +
+
+ ); +}`; + +function useMainNavOpen() { + const [open, setOpen] = React.useState(true); + React.useEffect(() => { + if (typeof window === "undefined") return; + const stored = window.localStorage.getItem(STORAGE_KEY); + if (stored !== null) setOpen(stored === "true"); + }, []); + React.useEffect(() => { + if (typeof window === "undefined") return; + window.localStorage.setItem(STORAGE_KEY, String(open)); + }, [open]); + return [open, setOpen] as const; +} + +function PreviewNavItem({ + icon: Icon, + label, + active, + open, +}: { + icon: React.ComponentType<{ className?: string }>; + label: string; + active?: boolean; + open: boolean; +}) { + return ( +
+ + + {open && {label}} + + {!open && ( + + {label} + + )} +
+ ); +} + +function PreviewUserMenu({ open }: { open: boolean }) { + const email = "you@example.com"; + return ( + + + + + + + + Signed in as + + + {email} + + + + + Profile + + + Settings + + +
+ +
+ + + Sign out + +
+
+ ); +} + +function DesktopPreview() { + const [open, setOpen] = useMainNavOpen(); + return ( +
+ +
+ {open + ? "When expanded, the collapse chevron sits next to the brand. State persists to localStorage." + : "When collapsed, hover an icon to pop a tooltip-style label outside the rail."} +
+
+ ); +} + +function MobilePreview() { + const [open, setOpen] = React.useState(false); + return ( +
+ {/* Fake page surface with hamburger fixed in the top-right corner */} +
+ + Acme + + +
+
+ Tap the menu to open the drawer +
+ + {open && ( +
+
setOpen(false)} + /> + +
+ )} +
+ ); +} + +export function MainNavSection() { + return ( + + A vertical rail at the left edge of the shell. The rail has two + fixed widths — collapsed (w-14, icons only) and + expanded (w-56, icons plus labels) — and a chevron + toggle switches between them. When expanded, the toggle sits in + the brand row at the top; when collapsed, an expand chevron sits + at the very bottom of the rail. The choice is persisted to{" "} + localStorage. When collapsed, hovering an item pops a + tooltip-style label out to the right of the rail without resizing + it. A user-account dropdown above the bottom edge holds a + signed-in-as label, Profile / Settings, the theme toggle, and Sign + out — and shows the avatar plus email when expanded, avatar only + when collapsed. On screens narrower than lg, the rail + is hidden in favor of a hamburger button fixed to the top-right + corner that opens the same nav as a slide-in drawer. + + } + whenToUse={ +
    +
  • One per shell. Always present and sticky on desktop.
  • +
  • Top-level destinations only — secondary items belong in sub-nav tabs.
  • +
  • 4–8 items max; beyond that, group with hairline separators.
  • +
+ } + whenNotToUse={ +
    +
  • For deep, multi-level menus — use sub-nav tabs or a command palette.
  • +
  • Marketing/landing pages — those use a horizontal top nav.
  • +
+ } + preview={ +
+
+

+ Desktop — toggle between collapsed and expanded +

+ +
+
+

+ Mobile — hamburger fixed to the top-right corner opens a drawer +

+ +
+
+ } + code={code} + options={ +
    +
  • + Width: w-14 collapsed,{" "} + w-56 expanded. Both are fixed; only the user-toggle + changes between them. +
  • +
  • + Persistence: open/closed state is written to{" "} + localStorage under main-nav-open{" "} + (rename per app). Hydrate inside useEffect so SSR + stays deterministic. +
  • +
  • + Toggle position: the collapse chevron lives in + the brand row when the rail is expanded (next to the brand on + the right). When the rail is collapsed there's no room for it + there, so the expand chevron sits at the very bottom of the + rail under the user menu. +
  • +
  • + Active item highlight (collapsed): each item is + a 36px square pill (h-9 w-9) centered with{" "} + mx-auto inside the 40px-wide nav column — keeps the{" "} + bg-accent-faded highlight as a square rather than a + full-width strip. +
  • +
  • + Floating labels (collapsed): each item is its + own group/nav-item with an{" "} + absolute left-full tooltip-style label. The label + is pointer-events-none and only opacity-toggles, so + the rail width is never disturbed. +
  • +
  • + Account menu: a Radix{" "} + DropdownMenu with{" "} + side="top" align="start" so it slides up from the + bottom-left corner. Contents (top to bottom): signed-in-as + label, Profile, Settings, separator, the{" "} + <ThemeToggle block /> primitive, separator, + Sign out. Trigger shows avatar + email when expanded, avatar + only when collapsed. +
  • +
  • + Mobile: hide the rail with{" "} + hidden lg:flex, render a hamburger fixed to{" "} + right-3 top-3 with z-30 and{" "} + bg-page so it stays readable while the page + scrolls. The drawer is w-64 with its own brand row + and close button at the top. +
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/structure/PageHeadersSection.tsx b/app/frontend/components/design-system/sections/structure/PageHeadersSection.tsx new file mode 100644 index 0000000..755a1e4 --- /dev/null +++ b/app/frontend/components/design-system/sections/structure/PageHeadersSection.tsx @@ -0,0 +1,181 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Bell, Search } from "lucide-react"; + +const code = `{/* Basic — title row only */} +
+
+
+

Projects

+

Everything your team is working on.

+
+ + {/* Right-side slot — buttons, icons, search, links */} +
+ + + +
+
+
+ +{/* With tabs — appends sub-navigation, results in two horizontal lines */} +
+
+
+

Acme Inc.

+

Workspace settings and billing.

+
+
+ +
+
+ +
`; + +export function PageHeadersSection() { + return ( + + The titled banner at the top of a page's main content area. Holds + the page title, optional supporting copy, and a flexible right-side + slot for actions. Optionally appends sub-navigation tabs below the + title row to produce a two-line header. + + } + whenToUse={ +
    +
  • Every primary page inside the shell.
  • +
  • Right-side slot accepts buttons, icons, search inputs, links — use it for primary and secondary page actions.
  • +
  • Use the "with tabs" variant when the page has sub-views (settings, profile, project sections).
  • +
+ } + whenNotToUse={ +
    +
  • Dialog/sheet contents — use DialogTitle instead.
  • +
  • Dense list views with tightly-packed filters — use a thinner toolbar pattern.
  • +
+ } + preview={ +
+
+

+ Basic — with right-side actions +

+
+
+
+

Projects

+

Everything your team is working on.

+
+
+ + + +
+
+
+
+ +
+

+ With search in the right slot +

+
+
+
+

Members

+

+ Manage who has access to this workspace. +

+
+
+
+ + +
+ +
+
+
+
+ +
+

+ With sub-navigation tabs (two horizontal lines) +

+
+
+
+

Acme Inc.

+

Workspace settings and billing.

+
+
+ +
+
+ +
+
+
+ } + code={code} + options={ +
    +
  • + Right-side slot: a flex row that accepts any + mix of buttons, icon buttons, links, badges, or search inputs. + Place primary CTA at the far right; secondary actions to its + left. +
  • +
  • + With breadcrumb: prepend a small breadcrumb row + above the title. +
  • +
  • + With tabs: append the Sub navigation pattern + below the title row's border-b — the tabs row gets + its own border-b, producing two horizontal lines + with the active tab's underline merging into the lower one. +
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/structure/ShellsSection.tsx b/app/frontend/components/design-system/sections/structure/ShellsSection.tsx new file mode 100644 index 0000000..cbf43c0 --- /dev/null +++ b/app/frontend/components/design-system/sections/structure/ShellsSection.tsx @@ -0,0 +1,95 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; + +const code = `
+ {/* Main navigation rail — see Main navigation section. Hidden below lg; + a hamburger fixed to the top-right opens the mobile drawer instead. */} + +
+
+ {/* Page header (optional with sub-nav tabs) → page content */} +
+
+
`; + +export function ShellsSection() { + return ( + + The outermost page frame. A shell is a full-bleed{" "} + bg-page container with the main-navigation rail on + the left and a content area on the right. The rail toggles + between collapsed (w-14) and expanded (w-56) + via a chevron button (state persisted to localStorage), + and is hidden entirely below the lg breakpoint in + favor of a fixed hamburger and slide-in drawer. Sub-navigation + lives inside page headers as horizontal tabs (not in the shell). + + } + whenToUse={ +
    +
  • Every authenticated screen.
  • +
  • Pair with a Page header at the top of the main content area.
  • +
+ } + whenNotToUse={ +
    +
  • Marketing/landing pages — those use a dedicated marketing shell with a horizontal top nav.
  • +
  • Modals/sheets — they layer on top of the shell, not replace it.
  • +
+ } + preview={ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ } + code={code} + options={ +
    +
  • + Rail-only: just the main-nav rail and a content + column. Default for app screens. +
  • +
  • + With sub-nav: page header inside{" "} + main appends sub-navigation tabs — see Page headers + "with tabs" variant. No second sidebar needed. +
  • +
  • + Content max-width: wrap children in{" "} + mx-auto max-w-4xl so long-form content stays + readable. The shell itself is full-bleed; the constraint lives + on the inner container. +
  • +
  • + Mobile: hide the rail with{" "} + hidden lg:flex and render a hamburger fixed to the + top-right of the viewport (fixed right-3 top-3 z-30) + that opens the same nav as a slide-in drawer overlay. +
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/design-system/sections/structure/SubNavSection.tsx b/app/frontend/components/design-system/sections/structure/SubNavSection.tsx new file mode 100644 index 0000000..c3deb1a --- /dev/null +++ b/app/frontend/components/design-system/sections/structure/SubNavSection.tsx @@ -0,0 +1,114 @@ +import { SectionShell } from "@/components/design-system/SectionShell"; + +const code = ``; + +export function SubNavSection() { + return ( + + Horizontal tab list for navigating within a single section of the + app (settings sub-pages, project tabs, profile sections). The + active tab's underline merges with the row's bottom hairline. An + optional right-side slot accepts secondary links, filters, search + fields, or small actions. + + } + whenToUse={ +
    +
  • When a page has 3+ sibling sub-views with stable structure.
  • +
  • Append to a Page header (see the "with tabs" variant).
  • +
  • Right-side slot is optional — leave empty when not needed.
  • +
+ } + whenNotToUse={ +
    +
  • For 1–2 sub-pages — just link them inline in the page body.
  • +
  • For dynamic, list-of-things navigation — use a Listing.
  • +
  • For top-level app destinations — those belong in main navigation.
  • +
+ } + preview={ +
+
+

+ Tabs only +

+ +
+ +
+

+ With right-side content +

+ +
+
+ } + code={code} + options={ +
    +
  • + Active state:{" "} + border-b-2 border-accent text-accent font-medium{" "} + on the active tab; -mb-px overlaps the parent's + hairline so the underlines merge into one line. +
  • +
  • + Right-side slot: any flex children — links, + filters, search fields, small icon buttons. Use{" "} + pb-2 to keep them visually centered against the tab + text baseline. +
  • +
  • + Pair with Page header: drop the parent{" "} + border-b off the page header, then place this nav + beneath it. Result: two horizontal lines. +
  • +
+ } + /> + ); +} diff --git a/app/frontend/components/ui/badge.tsx b/app/frontend/components/ui/badge.tsx new file mode 100644 index 0000000..e38bcd9 --- /dev/null +++ b/app/frontend/components/ui/badge.tsx @@ -0,0 +1,41 @@ +// bm-design-system: badge primitive +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const badgeVariants = cva( + "inline-flex items-center gap-1 rounded-full px-2.5 py-1.5 text-xs font-medium leading-none", + { + variants: { + tone: { + neutral: "bg-surface text-ink-body border border-hairline", + accent: "bg-accent-faded text-accent", + signal: "bg-signal-faded text-signal-darker", + muted: "bg-transparent text-ink-muted border border-hairline", + solid: "bg-accent text-page", + }, + }, + defaultVariants: { + tone: "neutral", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +const Badge = React.forwardRef( + ({ className, tone, ...props }, ref) => { + return ( + + ); + }, +); +Badge.displayName = "Badge"; + +export { Badge, badgeVariants }; diff --git a/app/frontend/components/ui/button.tsx b/app/frontend/components/ui/button.tsx index 21409a0..6e6837f 100644 --- a/app/frontend/components/ui/button.tsx +++ b/app/frontend/components/ui/button.tsx @@ -1,60 +1,53 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" +// bm-design-system: button primitive +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-page disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", - outline: - "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: - "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", - link: "text-primary underline-offset-4 hover:underline", + primary: "bg-accent text-page hover:bg-accent/90", + secondary: "bg-surface text-ink-display border border-hairline hover:bg-hairline/40", + ghost: "text-ink-body hover:bg-surface", + soft: "bg-accent-faded text-accent hover:bg-accent-faded/80", + danger: "bg-signal text-signal-darker hover:bg-signal/90", + link: "text-accent underline-offset-2 hover:underline p-0 h-auto", }, size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", - sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", - lg: "h-10 rounded-md px-6 has-[>svg]:px-4", - icon: "size-9", - "icon-sm": "size-8", - "icon-lg": "size-10", + sm: "h-8 px-3 text-sm", + md: "h-10 px-4 text-sm", + lg: "h-11 px-5 text-base", + icon: "h-10 w-10", }, }, defaultVariants: { - variant: "default", - size: "default", + variant: "primary", + size: "md", }, - } -) - -function Button({ - className, - variant, - size, - asChild = false, - ...props -}: React.ComponentProps<"button"> & - VariantProps & { - asChild?: boolean - }) { - const Comp = asChild ? Slot : "button" + }, +); - return ( - - ) +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; } -export { Button, buttonVariants } +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/app/frontend/components/ui/checkbox.tsx b/app/frontend/components/ui/checkbox.tsx new file mode 100644 index 0000000..c1e0372 --- /dev/null +++ b/app/frontend/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +// bm-design-system: checkbox primitive +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export interface CheckboxProps + extends Omit, "type"> {} + +const Checkbox = React.forwardRef( + ({ className, ...props }, ref) => { + return ( + + ); + }, +); +Checkbox.displayName = "Checkbox"; + +export { Checkbox }; diff --git a/app/frontend/components/ui/dialog.tsx b/app/frontend/components/ui/dialog.tsx new file mode 100644 index 0000000..0e7eb6a --- /dev/null +++ b/app/frontend/components/ui/dialog.tsx @@ -0,0 +1,119 @@ +// bm-design-system: dialog primitive +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; +const DialogTrigger = DialogPrimitive.Trigger; +const DialogPortal = DialogPrimitive.Portal; +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + size?: "sm" | "md" | "lg"; + } +>(({ className, children, size = "md", ...props }, ref) => { + const sizeClass = { + sm: "max-w-md", + md: "max-w-lg", + lg: "max-w-2xl", + }[size]; + return ( + + + + {children} + + + Close + + + + ); +}); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, + DialogClose, +}; diff --git a/app/frontend/components/ui/dropdown-menu.tsx b/app/frontend/components/ui/dropdown-menu.tsx index 37fb75f..ec6d22b 100644 --- a/app/frontend/components/ui/dropdown-menu.tsx +++ b/app/frontend/components/ui/dropdown-menu.tsx @@ -1,106 +1,88 @@ -import * as React from "react" -import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +// bm-design-system: dropdown-menu primitive +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { cn } from "@/lib/utils"; -import { cn } from "@/lib/utils" +const DropdownMenu = DropdownMenuPrimitive.Root; +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; +const DropdownMenuGroup = DropdownMenuPrimitive.Group; -function DropdownMenu({ - ...props -}: React.ComponentProps) { - return -} - -function DropdownMenuTrigger({ - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function DropdownMenuContent({ - className, - sideOffset = 4, - ...props -}: React.ComponentProps) { - return ( - - - - ) -} - -function DropdownMenuItem({ - className, - inset, - variant = "default", - ...props -}: React.ComponentProps & { - inset?: boolean - variant?: "default" | "destructive" -}) { - return ( - , + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 6, ...props }, ref) => ( + + - ) -} + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; -function DropdownMenuLabel({ - className, - inset, - ...props -}: React.ComponentProps & { - inset?: boolean -}) { - return ( - - ) -} +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + destructive?: boolean; + } +>(({ className, destructive, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; -function DropdownMenuSeparator({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; export { DropdownMenu, DropdownMenuTrigger, + DropdownMenuPortal, + DropdownMenuGroup, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, -} +}; diff --git a/app/frontend/components/ui/input.tsx b/app/frontend/components/ui/input.tsx index 03295ca..a27a91c 100644 --- a/app/frontend/components/ui/input.tsx +++ b/app/frontend/components/ui/input.tsx @@ -1,21 +1,27 @@ -import * as React from "react" +// bm-design-system: input primitive +import * as React from "react"; +import { cn } from "@/lib/utils"; -import { cn } from "@/lib/utils" +export interface InputProps extends React.InputHTMLAttributes {} -function Input({ className, type, ...props }: React.ComponentProps<"input">) { - return ( - - ) -} +const Input = React.forwardRef( + ({ className, type = "text", ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; -export { Input } +export { Input }; diff --git a/app/frontend/components/ui/label.tsx b/app/frontend/components/ui/label.tsx deleted file mode 100644 index ef7133a..0000000 --- a/app/frontend/components/ui/label.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" - -import { cn } from "@/lib/utils" - -function Label({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -export { Label } diff --git a/app/frontend/components/ui/radio.tsx b/app/frontend/components/ui/radio.tsx new file mode 100644 index 0000000..806410c --- /dev/null +++ b/app/frontend/components/ui/radio.tsx @@ -0,0 +1,48 @@ +// bm-design-system: radio primitive +import * as React from "react"; +import { cn } from "@/lib/utils"; + +export interface RadioProps + extends Omit, "type"> {} + +const Radio = React.forwardRef( + ({ className, ...props }, ref) => { + return ( + + ); + }, +); +Radio.displayName = "Radio"; + +interface RadioGroupProps extends React.HTMLAttributes { + orientation?: "vertical" | "horizontal"; +} + +const RadioGroup = React.forwardRef( + ({ className, orientation = "vertical", ...props }, ref) => ( +
+ ), +); +RadioGroup.displayName = "RadioGroup"; + +export { Radio, RadioGroup }; diff --git a/app/frontend/components/ui/rich-text-field.tsx b/app/frontend/components/ui/rich-text-field.tsx new file mode 100644 index 0000000..c0132de --- /dev/null +++ b/app/frontend/components/ui/rich-text-field.tsx @@ -0,0 +1,70 @@ +// bm-design-system: rich-text-field primitive +import * as React from "react"; +import { Crepe } from "@milkdown/crepe"; +import "@milkdown/crepe/theme/common/style.css"; +import "@milkdown/crepe/theme/frame.css"; +import { cn } from "@/lib/utils"; + +export interface RichTextFieldProps { + defaultValue?: string; + onChange?: (markdown: string) => void; + placeholder?: string; + readOnly?: boolean; + className?: string; +} + +const RichTextField = React.forwardRef( + ( + { defaultValue = "", onChange, placeholder, readOnly = false, className }, + ref, + ) => { + const localRef = React.useRef(null); + React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement); + + const onChangeRef = React.useRef(onChange); + React.useEffect(() => { + onChangeRef.current = onChange; + }, [onChange]); + + React.useEffect(() => { + const root = localRef.current; + if (!root) return; + + const crepe = new Crepe({ + root, + defaultValue, + featureConfigs: placeholder + ? { [Crepe.Feature.Placeholder]: { text: placeholder } } + : undefined, + }); + + crepe.setReadonly(readOnly); + + crepe.create().then(() => { + crepe.on((listener) => { + listener.markdownUpdated((_, markdown) => { + onChangeRef.current?.(markdown); + }); + }); + }); + + return () => { + crepe.destroy(); + }; + }, [defaultValue, placeholder, readOnly]); + + return ( +
+ ); + }, +); +RichTextField.displayName = "RichTextField"; + +export { RichTextField }; diff --git a/app/frontend/components/ui/select.tsx b/app/frontend/components/ui/select.tsx new file mode 100644 index 0000000..e202f29 --- /dev/null +++ b/app/frontend/components/ui/select.tsx @@ -0,0 +1,35 @@ +// bm-design-system: select primitive +import * as React from "react"; +import { ChevronDown } from "lucide-react"; +import { cn } from "@/lib/utils"; + +export interface SelectProps + extends React.SelectHTMLAttributes {} + +const Select = React.forwardRef( + ({ className, children, ...props }, ref) => { + return ( +
+ +
+ ); + }, +); +Select.displayName = "Select"; + +export { Select }; diff --git a/app/frontend/components/ui/theme-toggle.tsx b/app/frontend/components/ui/theme-toggle.tsx new file mode 100644 index 0000000..971a0f0 --- /dev/null +++ b/app/frontend/components/ui/theme-toggle.tsx @@ -0,0 +1,62 @@ +// bm-design-system: theme-toggle primitive +import * as React from "react"; +import { Monitor, Moon, Sun } from "lucide-react"; +import { useTheme, type Theme } from "@/lib/theme"; +import { cn } from "@/lib/utils"; + +interface ThemeOption { + value: Theme; + label: string; + Icon: React.ComponentType<{ className?: string }>; +} + +const OPTIONS: ThemeOption[] = [ + { value: "light", label: "Light mode", Icon: Sun }, + { value: "dark", label: "Dark mode", Icon: Moon }, + { value: "system", label: "System theme", Icon: Monitor }, +]; + +export interface ThemeToggleProps { + block?: boolean; + className?: string; +} + +export function ThemeToggle({ block = false, className }: ThemeToggleProps) { + const [theme, setTheme] = useTheme(); + + return ( +
+ {OPTIONS.map(({ value, label, Icon }) => { + const isActive = theme === value; + return ( + + ); + })} +
+ ); +} diff --git a/app/frontend/lib/theme.ts b/app/frontend/lib/theme.ts new file mode 100644 index 0000000..16e7895 --- /dev/null +++ b/app/frontend/lib/theme.ts @@ -0,0 +1,61 @@ +// bm-design-system: theme helper +import * as React from "react"; + +const STORAGE_KEY = "bm-ds-theme"; + +export type Theme = "light" | "dark" | "system"; + +function getSystemPreference(): "light" | "dark" { + if (typeof window === "undefined") return "light"; + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light"; +} + +export function getStoredTheme(): Theme { + if (typeof window === "undefined") return "system"; + const stored = window.localStorage.getItem(STORAGE_KEY); + if (stored === "light" || stored === "dark" || stored === "system") + return stored; + return "system"; +} + +export function applyTheme(theme: Theme) { + if (typeof document === "undefined") return; + const resolved = theme === "system" ? getSystemPreference() : theme; + const root = document.documentElement; + if (resolved === "dark") root.classList.add("dark"); + else root.classList.remove("dark"); +} + +export function useTheme() { + // Lazy initializer so the first render already reflects the stored choice. + // Without this, every remount (e.g. dropdown reopen) flashes "system" for + // one render before the effect resolves the stored value. + const [theme, setThemeState] = React.useState(() => getStoredTheme()); + + React.useEffect(() => { + // Reapply on mount in case another tab updated localStorage while this + // component was unmounted, and to ensure the class is consistent + // even if the inline boot script in the HTML layout was bypassed. + const stored = getStoredTheme(); + setThemeState(stored); + applyTheme(stored); + }, []); + + React.useEffect(() => { + if (theme !== "system") return; + const mql = window.matchMedia("(prefers-color-scheme: dark)"); + const handler = () => applyTheme("system"); + mql.addEventListener("change", handler); + return () => mql.removeEventListener("change", handler); + }, [theme]); + + const setTheme = React.useCallback((next: Theme) => { + setThemeState(next); + window.localStorage.setItem(STORAGE_KEY, next); + applyTheme(next); + }, []); + + return [theme, setTheme] as const; +} diff --git a/app/frontend/lib/utils.ts b/app/frontend/lib/utils.ts index bd0c391..a5ef193 100644 --- a/app/frontend/lib/utils.ts +++ b/app/frontend/lib/utils.ts @@ -1,6 +1,6 @@ -import { clsx, type ClassValue } from "clsx" -import { twMerge } from "tailwind-merge" +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) + return twMerge(clsx(inputs)); } diff --git a/app/frontend/styles/design-system.css b/app/frontend/styles/design-system.css new file mode 100644 index 0000000..1e4b6e4 --- /dev/null +++ b/app/frontend/styles/design-system.css @@ -0,0 +1,234 @@ +/* Web fonts are loaded via tags in the app's HTML head, not via + @import here. Bundler/PostCSS inlines this file mid-bundle, after + Tailwind's rules, which violates CSS spec (@import must come before any + other rules), so browsers silently drop the @import and the fonts never + load. See `references/styles/font-link-tags.html` for the link-tag + template the skill installs into the app's HTML layout. */ + +@custom-variant dark (&:where(.dark, .dark *)); + +@theme { + --font-display: 'Inter', ui-sans-serif, system-ui, sans-serif; + --font-sans: 'DM Sans', ui-sans-serif, system-ui, sans-serif; + + --color-page: #ffffff; + --color-surface: #f8fafc; + --color-hairline: #e2e8f0; + --color-ink-body: #334155; + --color-ink-display: #0f172a; + --color-ink-muted: #64748b; + --color-accent: #0891b2; + --color-accent-faded: #e1f2f6; + --color-accent-darker: #0e7490; + --color-signal: #fcd34d; + --color-signal-faded: #fffaea; + --color-signal-darker: #b45309; +} + +.dark { + --color-page: #020617; + --color-surface: #0f172a; + --color-hairline: #1e293b; + --color-ink-body: #e2e8f0; + --color-ink-display: #f8fafc; + --color-ink-muted: #94a3b8; + --color-accent: #0891b2; + --color-accent-faded: #031f33; + --color-accent-darker: #0e7490; + --color-signal: #fcd34d; + --color-signal-faded: #2f2b21; + --color-signal-darker: #b45309; +} + +@layer base { + html { + background-color: var(--color-page); + color: var(--color-ink-body); + font-family: var(--font-sans); + font-size: 106.25%; /* 17px base — scales every rem-based size */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + body { + background-color: var(--color-page); + color: var(--color-ink-body); + font-size: 1rem; + line-height: 1.6; + } + + h1, h2, h3, h4, h5, h6 { + color: var(--color-ink-display); + font-family: var(--font-display); + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.2; + } + + h1 { font-size: 2.25rem; font-weight: 700; letter-spacing: -0.03em; } + h2 { font-size: 1.7rem; letter-spacing: -0.03em; } + h3 { font-size: 1.3rem; } + h4 { font-size: 1.1rem; } + h5 { font-size: 1rem; } + h6 { font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-ink-muted); } + + a { + color: var(--color-accent); + text-decoration: none; + transition: color 120ms ease; + } + a:hover { color: color-mix(in oklab, var(--color-accent), black 15%); } + + p { color: var(--color-ink-body); } + + strong { color: var(--color-ink-display); font-weight: 600; } + + ul, ol { + color: var(--color-ink-body); + list-style: none; + padding-left: 0; + } + + li { padding-left: 0; } + li + li { margin-top: 0; } + + blockquote { + border-left: 3px solid var(--color-accent); + padding-left: 1rem; + color: var(--color-ink-muted); + font-style: italic; + } + + hr { + border: 0; + border-top: 1px solid var(--color-hairline); + margin: 2rem 0; + } + + code, pre, kbd, samp { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + } +} + +.body-content > * + * { + margin-top: 1.5rem; +} + +.body-content > h2:not(:first-child), +.body-content > h3:not(:first-child), +.body-content > h4:not(:first-child) { + padding-top: 1rem; +} + +.body-content ul, +.body-content ol { + padding-left: 1.5rem; +} +.body-content ul { list-style: disc; } +.body-content ol { list-style: decimal; } +.body-content li { padding-left: 0.25rem; } +.body-content li + li { margin-top: 0.25rem; } + +.callout { + background-color: var(--color-surface); + border: 1px solid var(--color-hairline); + border-radius: 0.5rem; + padding: 1.25rem; +} + +html { + scroll-behavior: smooth; +} + +[id] { + scroll-margin-top: 5rem; +} + +/* Rich text (Milkdown Crepe) — match app fonts, tighter padding. + Selectors are scoped under `.bm-rich-text .milkdown ...` so they win + specificity ties against Crepe's own `.milkdown ...` rules (which load + after design-system.css in the bundle). The `--crepe-font-*` variables + are also redefined on `.milkdown` itself so any of Crepe's internal + `var(--crepe-font-*)` lookups resolve to our app fonts. */ +.bm-rich-text .milkdown { + --crepe-font-default: var(--font-sans); + --crepe-font-title: var(--font-display); + font-family: var(--font-sans); + color: var(--color-ink-body); + background: transparent; +} + +.bm-rich-text .milkdown .ProseMirror { + font-family: var(--font-sans); + color: var(--color-ink-body); + padding: 0.5rem 1.25rem; + outline: none; + min-height: 8rem; + max-width: none; +} + +.bm-rich-text .milkdown .ProseMirror > * { + margin-left: 0; + margin-right: 0; + max-width: none; +} + +.bm-rich-text .milkdown .ProseMirror > * + * { + margin-top: 0.75rem; +} + +.bm-rich-text .milkdown .ProseMirror h1, +.bm-rich-text .milkdown .ProseMirror h2, +.bm-rich-text .milkdown .ProseMirror h3, +.bm-rich-text .milkdown .ProseMirror h4, +.bm-rich-text .milkdown .ProseMirror h5, +.bm-rich-text .milkdown .ProseMirror h6 { + font-family: var(--font-display); + color: var(--color-ink-display); + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.2; + text-align: left; + padding: 0; +} + +.bm-rich-text .milkdown .ProseMirror h1 { font-size: 1.75rem; letter-spacing: -0.02em; } +.bm-rich-text .milkdown .ProseMirror h2 { font-size: 1.5rem; letter-spacing: -0.02em; } +.bm-rich-text .milkdown .ProseMirror h3 { font-size: 1.25rem; } +.bm-rich-text .milkdown .ProseMirror h4 { font-size: 1.125rem; } +.bm-rich-text .milkdown .ProseMirror h5 { font-size: 1rem; } +.bm-rich-text .milkdown .ProseMirror h6 { + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-ink-muted); +} + +.bm-rich-text .milkdown .ProseMirror p { + font-family: var(--font-sans); + color: var(--color-ink-body); + line-height: 1.6; +} + +.bm-rich-text .milkdown .ProseMirror ul, +.bm-rich-text .milkdown .ProseMirror ol { + padding-left: 1.5rem; +} +.bm-rich-text .milkdown .ProseMirror ul { list-style: disc; } +.bm-rich-text .milkdown .ProseMirror ol { list-style: decimal; } +.bm-rich-text .milkdown .ProseMirror li + li { margin-top: 0.25rem; } + +.bm-rich-text .milkdown .ProseMirror blockquote { + border-left: 3px solid var(--color-accent); + padding-left: 1rem; + color: var(--color-ink-muted); + font-style: italic; +} + +.bm-rich-text .milkdown .ProseMirror code { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + background: var(--color-surface); + padding: 0.125rem 0.25rem; + border-radius: 0.25rem; + font-size: 0.875em; +} diff --git a/app/frontend/types/inertia.ts b/app/frontend/types/inertia.ts index 90be436..70f22cd 100644 --- a/app/frontend/types/inertia.ts +++ b/app/frontend/types/inertia.ts @@ -4,6 +4,7 @@ export type CurrentUser = { id: number email: string timezone: string | null + admin: boolean } | null export type SharedProps = { diff --git a/app/javascript/entrypoints/application.css b/app/javascript/entrypoints/application.css index 7170240..d059471 100644 --- a/app/javascript/entrypoints/application.css +++ b/app/javascript/entrypoints/application.css @@ -1,129 +1,7 @@ @import "tailwindcss"; -@import "tw-animate-css"; -@custom-variant dark (&:is(.dark *)); +@source "../../**/*.{ts,tsx,js,jsx}"; -@source "../../frontend"; -@source "../pages"; - -@plugin "@tailwindcss/typography"; -@plugin "@tailwindcss/forms"; - -@theme inline { - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); -} - -:root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.92 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.32 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); -} - -@layer base { - html { - font-size: 17px; - } - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -} +/* bm-design-system:start */ +@import "../../frontend/styles/design-system.css"; +/* bm-design-system:end */ diff --git a/app/javascript/entrypoints/inertia.ts b/app/javascript/entrypoints/inertia.ts index 0c7de55..f746c1f 100644 --- a/app/javascript/entrypoints/inertia.ts +++ b/app/javascript/entrypoints/inertia.ts @@ -1,25 +1,13 @@ import { createInertiaApp } from '@inertiajs/react' -import { createElement, ReactNode } from 'react' +import { createElement, type ReactNode } from 'react' import { createRoot } from 'react-dom/client' -import './application.css' -// Temporary type definition, until @inertiajs/react provides one type ResolvedComponent = { default: ReactNode layout?: (page: ReactNode) => ReactNode } createInertiaApp({ - // Set default page title - // see https://inertia-rails.dev/guide/title-and-meta - // - // title: title => title ? `${title} - App` : 'App', - - // Disable progress bar - // - // see https://inertia-rails.dev/guide/progress-indicators - // progress: false, - resolve: (name) => { const pages = import.meta.glob('../pages/**/*.tsx', { eager: true, @@ -28,13 +16,6 @@ createInertiaApp({ if (!page) { console.error(`Missing Inertia page component: '${name}.tsx'`) } - - // To use a default layout, import the Layout component - // and use the following line. - // see https://inertia-rails.dev/guide/pages#default-layouts - // - // page.default.layout ||= (page) => createElement(Layout, null, page) - return page }, @@ -43,9 +24,7 @@ createInertiaApp({ createRoot(el).render(createElement(App, props)) } else { console.error( - 'Missing root element.\n\n' + - 'If you see this error, it probably means you load Inertia.js on non-Inertia pages.\n' + - 'Consider moving <%= vite_typescript_tag "inertia" %> to the Inertia-specific layout instead.', + 'Missing root element. Move `vite_typescript_tag "inertia"` into an Inertia-specific layout.', ) } }, diff --git a/app/javascript/pages/Dashboard.tsx b/app/javascript/pages/Dashboard.tsx index c2d61c3..417f1a5 100644 --- a/app/javascript/pages/Dashboard.tsx +++ b/app/javascript/pages/Dashboard.tsx @@ -1,8 +1,12 @@ -import { Head } from "@inertiajs/react" +import { Head, usePage } from "@inertiajs/react" +import { AppShell } from "@/components/AppShell" -import { AppShell } from "@/components/app-shell" +import type { PageProps } from "@/types/inertia" export default function Dashboard() { + const { props } = usePage() + const user = props.current_user + return ( <> @@ -10,8 +14,11 @@ export default function Dashboard() { - -

Welcome to your account.

+ +

Home

+

+ Welcome to your account{user?.email ? `, ${user.email}` : ""}. +

) diff --git a/app/javascript/pages/Home.tsx b/app/javascript/pages/Home.tsx index cb2d147..bcaa7c5 100644 --- a/app/javascript/pages/Home.tsx +++ b/app/javascript/pages/Home.tsx @@ -1,5 +1,4 @@ import { Link, Head } from "@inertiajs/react" - import { Button } from "@/components/ui/button" export default function Home() { @@ -16,19 +15,18 @@ export default function Home() { content="Starter landing page for the Build New Rails + Inertia template — replace this copy with the real product pitch." /> -
-
-

- Hello world. -

-
- - -
+
+

Hello world.

+

+ Starter landing page for the Build New Rails + Inertia template. +

+
+ +
diff --git a/app/javascript/pages/Profile.tsx b/app/javascript/pages/Profile.tsx deleted file mode 100644 index 80d5ca2..0000000 --- a/app/javascript/pages/Profile.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { FormEvent } from "react" -import { Head, useForm, usePage } from "@inertiajs/react" - -import { AppShell } from "@/components/app-shell" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import type { PageProps } from "@/types/inertia" - -export default function Profile() { - const { props } = usePage() - const user = props.current_user - const errors = props.errors ?? {} - - const emailForm = useForm({ email: user?.email ?? "" }) - const passwordForm = useForm({ current_password: "", password: "" }) - - const submitEmail = (e: FormEvent) => { - e.preventDefault() - emailForm.patch("/profile/email", { preserveScroll: true }) - } - - const submitPassword = (e: FormEvent) => { - e.preventDefault() - passwordForm.patch("/profile/password", { - preserveScroll: true, - onSuccess: () => passwordForm.reset(), - }) - } - - return ( - <> - - - - - - -
-
-

Profile

-

- Manage your email address and password. -

-
- - {props.flash?.notice && ( -

- {props.flash.notice} -

- )} - -
-
-

Email

-

- Change the email address used to log in. -

-
-
-
- - - emailForm.setData("email", e.target.value) - } - /> - {errors.email && ( -

- {errors.email} -

- )} -
-
- -
-
-
- -
-
-

Password

-

- Enter your current password to set a new one. -

-
-
-
- - - passwordForm.setData("current_password", e.target.value) - } - /> - {errors.current_password && ( -

- {errors.current_password} -

- )} -
-
- - - passwordForm.setData("password", e.target.value) - } - /> - {errors.password && ( -

{errors.password}

- )} -
-
- -
-
-
-
-
- - ) -} diff --git a/app/javascript/pages/Settings.tsx b/app/javascript/pages/Settings.tsx index 5430b40..c5cc1a6 100644 --- a/app/javascript/pages/Settings.tsx +++ b/app/javascript/pages/Settings.tsx @@ -1,6 +1,5 @@ import { Head } from "@inertiajs/react" - -import { AppShell } from "@/components/app-shell" +import { AppShell } from "@/components/AppShell" export default function Settings() { return ( @@ -10,13 +9,9 @@ export default function Settings() { - -
-

Settings

-

- Application settings will go here. -

-
+ +

Settings

+

Application settings will go here.

) diff --git a/app/javascript/pages/admin/design-system.tsx b/app/javascript/pages/admin/design-system.tsx new file mode 100644 index 0000000..1848ca8 --- /dev/null +++ b/app/javascript/pages/admin/design-system.tsx @@ -0,0 +1,21 @@ +import { Head } from "@inertiajs/react"; +import { DesignSystem } from "@/components/design-system/DesignSystem"; + +export default function AdminDesignSystem() { + return ( + <> + + + + + + + + ); +} diff --git a/app/javascript/pages/admin/users/Index.tsx b/app/javascript/pages/admin/users/Index.tsx new file mode 100644 index 0000000..0b49bfa --- /dev/null +++ b/app/javascript/pages/admin/users/Index.tsx @@ -0,0 +1,66 @@ +import { Head, Link } from "@inertiajs/react" +import { ChevronRight, User as UserIcon } from "lucide-react" +import { AdminShell } from "@/components/AdminShell" +import { Badge } from "@/components/ui/badge" + +type UserRow = { + id: number + email: string + admin: boolean + created_at: string +} + +export default function AdminUsersIndex({ users }: { users: UserRow[] }) { + return ( + <> + + + + + + +
+
+
+

Users

+

+ {users.length} {users.length === 1 ? "user" : "users"} in this app. +

+
+
+
+ +
    + {users.map((user) => ( +
  • + + +
    +
    + {user.email} +
    +
    + Joined {formatDate(user.created_at)} +
    +
    + {user.admin && Admin} + + +
  • + ))} +
+
+ + ) +} + +function formatDate(iso: string) { + return new Date(iso).toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + }) +} diff --git a/app/javascript/pages/admin/users/Show.tsx b/app/javascript/pages/admin/users/Show.tsx new file mode 100644 index 0000000..528741d --- /dev/null +++ b/app/javascript/pages/admin/users/Show.tsx @@ -0,0 +1,70 @@ +import { Head, Link } from "@inertiajs/react" +import { AdminShell } from "@/components/AdminShell" +import { Badge } from "@/components/ui/badge" + +type UserDetail = { + id: number + email: string + admin: boolean + timezone: string | null + created_at: string + updated_at: string +} + +export default function AdminUserShow({ user }: { user: UserDetail }) { + return ( + <> + + + + + + +
+
+
+

{user.email}

+

User #{user.id}

+
+
+ {user.admin && Admin} + + Back to users + +
+
+
+ +
+ + + + + +
+
+ + ) +} + +function DetailRow({ label, value }: { label: string; value: string }) { + return ( + <> +
{label}
+
{value}
+ + ) +} + +function formatDateTime(iso: string) { + return new Date(iso).toLocaleString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }) +} diff --git a/app/javascript/pages/auth/Login.tsx b/app/javascript/pages/auth/Login.tsx index 7474f32..fddc6f1 100644 --- a/app/javascript/pages/auth/Login.tsx +++ b/app/javascript/pages/auth/Login.tsx @@ -1,10 +1,9 @@ import { FormEvent } from "react" import { Head, Link, useForm, usePage } from "@inertiajs/react" - -import { AuthCard } from "@/components/auth-card" +import { AuthShell } from "@/components/AuthShell" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" + import type { PageProps } from "@/types/inertia" export default function Login() { @@ -25,30 +24,31 @@ export default function Login() { - - Don't have an account?{" "} - - Sign up - - - } - > + +
+

Log in

+

+ Don't have an account? Sign up +

+
+ {props.flash?.notice && ( -

+

{props.flash.notice}

)} {baseError && ( -

- {baseError} -

+

{baseError}

)} -
-
- + + +
+ form.setData("email", e.target.value)} />
-
-
- - - Forgot password? - -
+
+ form.setData("password", e.target.value)} />
- +
+ + + Forgot password? + +
- + ) } diff --git a/app/javascript/pages/auth/PasswordEdit.tsx b/app/javascript/pages/auth/PasswordEdit.tsx index 5cefe9b..63d36cd 100644 --- a/app/javascript/pages/auth/PasswordEdit.tsx +++ b/app/javascript/pages/auth/PasswordEdit.tsx @@ -1,10 +1,9 @@ import { FormEvent } from "react" import { Head, useForm, usePage } from "@inertiajs/react" - -import { AuthCard } from "@/components/auth-card" +import { AuthShell } from "@/components/AuthShell" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" + import type { PageProps } from "@/types/inertia" type Props = { token: string } @@ -27,10 +26,16 @@ export default function PasswordEdit({ token }: Props) { - -
-
- + +

Choose a new password

+ +
+ form.setData("password", e.target.value)} /> {errors.password && ( -

{errors.password}

+

{errors.password}

)}
- - +
) } diff --git a/app/javascript/pages/auth/PasswordNew.tsx b/app/javascript/pages/auth/PasswordNew.tsx index e9007f2..beb8e08 100644 --- a/app/javascript/pages/auth/PasswordNew.tsx +++ b/app/javascript/pages/auth/PasswordNew.tsx @@ -1,10 +1,9 @@ import { FormEvent } from "react" import { Head, Link, useForm, usePage } from "@inertiajs/react" - -import { AuthCard } from "@/components/auth-card" +import { AuthShell } from "@/components/AuthShell" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" + import type { PageProps } from "@/types/inertia" export default function PasswordNew() { @@ -23,18 +22,22 @@ export default function PasswordNew() { - + +

Reset your password

+

We'll email you a link to set a new password.

+ {props.flash?.notice && ( -

- {props.flash.notice} -

+

{props.flash.notice}

)} -
-
- + + +
+ form.setData("email", e.target.value)} />
- -

- - Back to log in - -

- + +

+ Back to log in +

+ ) } diff --git a/app/javascript/pages/auth/Signup.tsx b/app/javascript/pages/auth/Signup.tsx index 97d8013..8a3a1f3 100644 --- a/app/javascript/pages/auth/Signup.tsx +++ b/app/javascript/pages/auth/Signup.tsx @@ -1,10 +1,9 @@ import { FormEvent, useEffect } from "react" import { Head, Link, useForm, usePage } from "@inertiajs/react" - -import { AuthCard } from "@/components/auth-card" +import { AuthShell } from "@/components/AuthShell" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" + import type { PageProps } from "@/types/inertia" export default function Signup() { @@ -31,20 +30,22 @@ export default function Signup() { - - Already have an account?{" "} - - Log in - - - } - > -
-
- + +
+

Create your account

+

+ Already have an account? Log in +

+
+ + +
+ form.setData("email", e.target.value)} /> {errors.email && ( -

{errors.email}

+

{errors.email}

)}
-
- +
+ form.setData("password", e.target.value)} /> {errors.password && ( -

{errors.password}

+

{errors.password}

)}
- - + ) } diff --git a/app/javascript/pages/profile/Details.tsx b/app/javascript/pages/profile/Details.tsx new file mode 100644 index 0000000..14734c0 --- /dev/null +++ b/app/javascript/pages/profile/Details.tsx @@ -0,0 +1,71 @@ +import { FormEvent } from "react" +import { Head, useForm, usePage } from "@inertiajs/react" +import { AppShell } from "@/components/AppShell" +import { PageHeader } from "@/components/PageHeader" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { ProfileSubNav } from "./ProfileSubNav" + +import type { PageProps } from "@/types/inertia" + +export default function ProfileDetails() { + const { props } = usePage() + const user = props.current_user + const errors = props.errors ?? {} + + const emailForm = useForm({ email: user?.email ?? "" }) + + const submit = (e: FormEvent) => { + e.preventDefault() + emailForm.patch("/profile/email", { preserveScroll: true }) + } + + return ( + <> + + + + + + + } + /> + + {props.flash?.notice && ( +

{props.flash.notice}

+ )} + +
+
+
+ + emailForm.setData("email", e.target.value)} + /> + {errors.email && ( +

{errors.email}

+ )} +
+ +
+
+
+ + ) +} diff --git a/app/javascript/pages/profile/Password.tsx b/app/javascript/pages/profile/Password.tsx new file mode 100644 index 0000000..be75f1a --- /dev/null +++ b/app/javascript/pages/profile/Password.tsx @@ -0,0 +1,97 @@ +import { FormEvent } from "react" +import { Head, useForm, usePage } from "@inertiajs/react" +import { AppShell } from "@/components/AppShell" +import { PageHeader } from "@/components/PageHeader" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { ProfileSubNav } from "./ProfileSubNav" + +import type { PageProps } from "@/types/inertia" + +export default function ProfilePassword() { + const { props } = usePage() + const errors = props.errors ?? {} + + const passwordForm = useForm({ current_password: "", password: "" }) + + const submit = (e: FormEvent) => { + e.preventDefault() + passwordForm.patch("/profile/password", { + preserveScroll: true, + onSuccess: () => passwordForm.reset(), + }) + } + + return ( + <> + + + + + + + } + /> + + {props.flash?.notice && ( +

{props.flash.notice}

+ )} + +
+
+
+ + + passwordForm.setData("current_password", e.target.value) + } + /> + {errors.current_password && ( +

{errors.current_password}

+ )} +
+
+ + + passwordForm.setData("password", e.target.value) + } + /> + {errors.password && ( +

{errors.password}

+ )} +
+ +
+
+
+ + ) +} diff --git a/app/javascript/pages/profile/ProfileSubNav.tsx b/app/javascript/pages/profile/ProfileSubNav.tsx new file mode 100644 index 0000000..6f9586f --- /dev/null +++ b/app/javascript/pages/profile/ProfileSubNav.tsx @@ -0,0 +1,33 @@ +import { Link } from "@inertiajs/react" +import { cn } from "@/lib/utils" + +const TABS = [ + { href: "/profile", label: "My details" }, + { href: "/profile/password", label: "Password" }, +] as const + +export function ProfileSubNav({ active }: { active: "details" | "password" }) { + const activeHref = active === "details" ? "/profile" : "/profile/password" + + return ( + + ) +} diff --git a/app/javascript/entrypoints/ssr.tsx b/app/javascript/ssr/ssr.tsx similarity index 100% rename from app/javascript/entrypoints/ssr.tsx rename to app/javascript/ssr/ssr.tsx diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 34aff33..da15110 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -3,23 +3,11 @@ <%= content_for(:title) || "Build New" %> - <%= csrf_meta_tags %> <%= csp_meta_tag %> - - <%= yield :head %> <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %> @@ -29,8 +17,27 @@ + <%# bm-design-system:start %> + + + + + <%# bm-design-system:end %> + <%= vite_react_refresh_tag %> <%= vite_client_tag %> + <%= vite_stylesheet_tag "application" %> <%= vite_typescript_tag "inertia" %> <%= inertia_ssr_head %> diff --git a/bin/dev-ssr b/bin/dev-ssr new file mode 100755 index 0000000..1cf869d --- /dev/null +++ b/bin/dev-ssr @@ -0,0 +1,30 @@ +#!/usr/bin/env sh + +export PORT="${PORT:-3000}" +export INERTIA_SSR=1 + +# Build the SSR bundle once before starting the process group, so the +# Node SSR server has something to load on first start. The watcher in +# Procfile.ssr keeps it fresh after that. +echo "Building SSR bundle..." +bin/vite build --ssr || exit 1 + +if command -v overmind 1> /dev/null 2>&1 +then + overmind start -f Procfile.ssr "$@" + exit $? +fi + +if command -v hivemind 1> /dev/null 2>&1 +then + echo "Hivemind is installed. Running the application with Hivemind..." + exec hivemind Procfile.ssr "$@" + exit $? +fi + +if gem list --no-installed --exact --silent foreman; then + echo "Installing foreman..." + gem install foreman +fi + +foreman start -f Procfile.ssr "$@" diff --git a/components.json b/components.json deleted file mode 100644 index 09696ef..0000000 --- a/components.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://ui.shadcn.com/schema.json", - "style": "new-york", - "rsc": false, - "tsx": true, - "tailwind": { - "config": "", - "css": "app/javascript/entrypoints/application.css", - "baseColor": "neutral", - "cssVariables": true, - "prefix": "" - }, - "iconLibrary": "lucide", - "aliases": { - "components": "@/components", - "utils": "@/lib/utils", - "ui": "@/components/ui", - "lib": "@/lib", - "hooks": "@/hooks" - }, - "registries": {} -} diff --git a/conductor.json b/conductor.json index a3bbd34..4fc0525 100644 --- a/conductor.json +++ b/conductor.json @@ -1,6 +1,6 @@ { "scripts": { - "setup": "# bundle, create db, migrate, run seeds\nbin/setup", + "setup": "# bundle, create db, migrate, run seeds\nbin/setup\n\n# install frontend deps\nnpm install", "run": "# Kill whatever is running on port 3000\nlsof -ti :3000 | xargs kill -9\nbin/dev", "archive": "# Frontend deps\nrm -rf node_modules\n\n# Vite build output\nrm -rf public/vite public/vite-ssr public/vite-dev\n\n# Rails tmp + logs\nrm -rf tmp/cache tmp/pids tmp/sockets tmp/storage\nrm -f log/*.log" } diff --git a/config/database.yml b/config/database.yml index 04719f2..9f57633 100644 --- a/config/database.yml +++ b/config/database.yml @@ -1,48 +1,41 @@ -# PostgreSQL. Versions 9.3 and up are supported. +# PostgreSQL. Versions 10 and up are supported. # # Install the pg driver: # gem install pg # On macOS with Homebrew: -# brew install postgresql -# On Debian/Ubuntu: -# apt-get install libpq-dev +# brew install postgresql@14 # -<% - # Derive the database name from the project directory so each template clone - # gets its own Postgres database instead of colliding on a shared name. - app_db_name = Rails.root.basename.to_s.downcase.gsub(/[^a-z0-9_]/, "_") -%> +# A single PostgreSQL database is shared by Active Record AND the Solid trifecta +# (Solid Queue, Solid Cache, Solid Cable). No separate cache/cable databases. default: &default adapter: postgresql encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - host: <%= ENV.fetch("DATABASE_HOST", "localhost") %> - username: <%= ENV.fetch("DATABASE_USERNAME", ENV["USER"]) %> - password: <%= ENV.fetch("DATABASE_PASSWORD", "") %> - # Disable GSSAPI/Kerberos negotiation in libpq. Apple's Kerberos plugins - # segfault in forked children (Solid Queue workers) on macOS. - gssencmode: disable + username: <%= ENV.fetch("DATABASE_USER") { ENV["USER"] } %> + password: <%= ENV["DATABASE_PASSWORD"] %> + host: <%= ENV.fetch("DATABASE_HOST") { "localhost" } %> + port: <%= ENV.fetch("DATABASE_PORT") { 5432 } %> development: <<: *default - database: <%= app_db_name %>_development + database: build_new_development # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default - database: <%= app_db_name %>_test + database: build_new_test # Solid Queue, Solid Cache, and Solid Cable all share this single primary database. +# In production, set DATABASE_URL or override the connection settings via env vars. staging: <<: *default - database: <%= app_db_name %>_staging + database: build_new_staging url: <%= ENV["DATABASE_URL"] %> production: <<: *default - database: <%= app_db_name %>_production + database: build_new_production url: <%= ENV["DATABASE_URL"] %> - diff --git a/config/puma.rb b/config/puma.rb index 8f393c3..7c1ac84 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -33,8 +33,10 @@ # Allow puma to be restarted by `bin/rails restart` command. plugin :tmp_restart -# Run the Solid Queue supervisor inside of Puma for single-server deployments -plugin :solid_queue +# Run the Solid Queue supervisor inside of Puma for single-server deployments. +# In development, bin/jobs runs as its own Procfile process, so skip it here to +# avoid duplicate supervisors (and a macOS fork-safety crash that takes Puma down). +plugin :solid_queue unless Rails.env.development? # Specify the PID file. Defaults to tmp/pids/server.pid in development. # In other environments, only set the PID file if requested. diff --git a/config/routes.rb b/config/routes.rb index 64cb15e..3ef30bf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -11,9 +11,15 @@ get "dashboard", to: "dashboard#show", as: :dashboard get "settings", to: "settings#show", as: :settings - get "profile", to: "profiles#show", as: :profile - patch "profile/email", to: "profiles#update_email", as: :profile_email - patch "profile/password", to: "profiles#update_password", as: :profile_password + namespace :admin do + get "design-system", to: "design_system#show", as: :design_system + resources :users, only: %i[ index show ] + end + + get "profile", to: "profiles#details", as: :profile + get "profile/password", to: "profiles#password", as: :profile_password + patch "profile/email", to: "profiles#update_email" + patch "profile/password", to: "profiles#update_password" mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development? diff --git a/db/migrate/20260423000004_create_users.rb b/db/migrate/20260423000004_create_users.rb index 61c272b..e48f27f 100644 --- a/db/migrate/20260423000004_create_users.rb +++ b/db/migrate/20260423000004_create_users.rb @@ -4,6 +4,7 @@ def change t.string :email, null: false t.string :password_digest, null: false t.string :timezone + t.boolean :admin, null: false, default: false t.timestamps end diff --git a/db/schema.rb b/db/schema.rb index ba0e719..4fd9a11 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,7 +15,7 @@ enable_extension "pg_catalog.plpgsql" create_table "sessions", force: :cascade do |t| - t.bigint "user_id", null: false + t.integer "user_id", null: false t.string "ip_address" t.string "user_agent" t.datetime "created_at", null: false @@ -169,6 +169,7 @@ t.string "email", null: false t.string "password_digest", null: false t.string "timezone" + t.boolean "admin", default: false, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_users_on_email", unique: true diff --git a/db/seeds.rb b/db/seeds.rb index 4fbd6ed..80616ca 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -7,3 +7,15 @@ # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| # MovieGenre.find_or_create_by!(name: genre_name) # end + +User.find_or_create_by!(email: "user@test.com") do |user| + user.password = "test123" + user.timezone = "America/New_York" + user.admin = false +end + +User.find_or_create_by!(email: "admin@test.com") do |user| + user.password = "test123" + user.timezone = "America/New_York" + user.admin = true +end diff --git a/package-lock.json b/package-lock.json index 63596b0..4f4a266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,29 +7,32 @@ "name": "build-new", "dependencies": { "@inertiajs/react": "^2.2.15", + "@milkdown/core": "^7.20.0", + "@milkdown/crepe": "^7.20.0", + "@milkdown/react": "^7.20.0", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-slot": "^1.2.4", - "@tailwindcss/forms": "^0.5.10", - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.16", + "@tailwindcss/vite": "^4.3.0", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@vitejs/plugin-react": "^5.1.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "lucide-react": "^0.552.0", + "lucide-react": "^1.14.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.16", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.3.0", "typescript": "^5.9.3" }, "devDependencies": { "@types/node": "^25.6.0", - "tw-animate-css": "^1.4.0", "vite": "^7.2.0", "vite-plugin-ruby": "^5.1.1" + }, + "engines": { + "node": ">=22.12.0" } }, "node_modules/@babel/code-frame": { @@ -206,12 +209,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -283,9 +286,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -295,6 +298,419 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.2", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.2.tgz", + "integrity": "sha512-G5FPkgIiLjOgZMjqVjvuKQ1rGPtHogLldJr33eFJdVLtmwY+giGrlv/ewljLz6b9BSQLkjxuwBc6g6omDM+YxQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", + "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.6.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-angular": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz", + "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.3" + } + }, + "node_modules/@codemirror/lang-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/cpp": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", + "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-jinja": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.1.tgz", + "integrity": "sha512-P5kyHLObzjtbGj16h+hyvZTxJhSjBEeSx4wMjbnAf3b0uwTy2+F0zGjMZL4PQOm/mh2eGZ5xUDVZXgwP783Nsw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-less": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", + "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-liquid": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.2.tgz", + "integrity": "sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", + "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/sass": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sql": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-vue": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", + "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-wast": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", + "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", + "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", + "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/language-data": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.2.tgz", + "integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-angular": "^0.1.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-go": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-jinja": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-less": "^6.0.0", + "@codemirror/lang-liquid": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sass": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-vue": "^0.1.1", + "@codemirror/lang-wast": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/legacy-modes": "^6.4.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz", + "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.6.tgz", + "integrity": "sha512-6Kp7r6XfCi/D/5sdXieMfg9pJU1bUEx96WITuLU6ESaKizCz0QHFMjY/TaFSbigDdEAIgi93itLBIUETP4oK+A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.42.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.7.0.tgz", + "integrity": "sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.37.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", + "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.42.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.42.1.tgz", + "integrity": "sha512-ToN3oFc0nsxNUYVF5P0ztLgbC4UPPjPtA9aKYhkOKQaZASpOUo6ISXyQLP66ctVwlDc+j6Jv0uK5IFALkiXztg==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.6.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -820,173 +1236,618 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/cpp": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.5.tgz", + "integrity": "sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==", "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, + "node_modules/@lezer/css": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz", + "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==", "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", "license": "MIT", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" } }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" } }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "node_modules/@lezer/java": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", + "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" } }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" } }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" } }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } + "dependencies": { + "@lezer/common": "^1.0.0" } }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "node_modules/@lezer/markdown": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/rust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", + "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/sass": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.1.0.tgz", + "integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@milkdown/components": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/components/-/components-7.20.0.tgz", + "integrity": "sha512-Qn91/oZugGjf17ARE51nbEsH4YklZQaomRSsfxOAtIcwGEJe5osq+zhhKGtgAYFfUb6rU3W86Pe4XDlXN6vFjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.5.1", + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/exception": "7.20.0", + "@milkdown/plugin-tooltip": "7.20.0", + "@milkdown/preset-commonmark": "7.20.0", + "@milkdown/preset-gfm": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/transformer": "7.20.0", + "@milkdown/utils": "7.20.0", + "@types/lodash-es": "^4.17.12", + "clsx": "^2.0.0", + "dompurify": "^3.2.5", + "lodash-es": "^4.17.21", + "nanoid": "^5.0.9", + "unist-util-visit": "^5.0.0", + "vue": "^3.5.20" }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true + "peerDependencies": { + "@codemirror/language": "^6", + "@codemirror/state": "^6", + "@codemirror/view": "^6" + } + }, + "node_modules/@milkdown/components/node_modules/nanoid": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" } }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "node_modules/@milkdown/core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/core/-/core-7.20.0.tgz", + "integrity": "sha512-X9LaUcIR4Y2oiY2J5tslavlPVOwIB3X8/9z1bOeBjlIPtr+urbkY7YEX86EeLV9LyRQ3+t+jXaLMCIjW1wsV6w==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" + "@milkdown/ctx": "7.20.0", + "@milkdown/exception": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/transformer": "7.20.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.3" + } + }, + "node_modules/@milkdown/crepe": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/crepe/-/crepe-7.20.0.tgz", + "integrity": "sha512-KT+oFF6Q7mI41z01c9v/wUUCyQ2f908TgOsa6mwi25yuxnxQxISZFCjRvlh0sc9p9D3CrMeuJWGCN6DialQdig==", + "license": "MIT", + "dependencies": { + "@codemirror/commands": "^6.2.4", + "@codemirror/language": "^6.10.1", + "@codemirror/language-data": "^6.3.1", + "@codemirror/state": "^6.4.1", + "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/view": "^6.16.0", + "@milkdown/kit": "7.20.0", + "@types/lodash-es": "^4.17.12", + "clsx": "^2.0.0", + "codemirror": "^6.0.1", + "katex": "^0.16.0", + "lodash-es": "^4.17.21", + "prosemirror-virtual-cursor": "^0.4.2", + "remark-math": "^6.0.0", + "unist-util-visit": "^5.0.0", + "vue": "^3.5.20" + } + }, + "node_modules/@milkdown/ctx": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/ctx/-/ctx-7.20.0.tgz", + "integrity": "sha512-LUK4xdsUngY2xCCvPTyHPifjAknJ5rE6VBjgsP+LySIUKeFUrhqzo/zz2vaOODKzm3DBMIhpZAoW3MAqxoMGIQ==", + "license": "MIT", + "dependencies": { + "@milkdown/exception": "7.20.0" + } + }, + "node_modules/@milkdown/exception": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/exception/-/exception-7.20.0.tgz", + "integrity": "sha512-u8EL7rbqgrWrPpkDhrxUYXauw2DO52JUQmuokrUZvqezmflo7pgIDCr+Rk6AQslzB4Xw+n9eYik5rXX3RXC7Qg==", + "license": "MIT" + }, + "node_modules/@milkdown/kit": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/kit/-/kit-7.20.0.tgz", + "integrity": "sha512-X74KMa0tcDAAMOE9aFtBRN+RCdD/HMXor5YN18e7d0pe4a65MGFklUGlcg1U6zEfeMMYeC3msNvMKLMwk3O5RA==", + "license": "MIT", + "dependencies": { + "@milkdown/components": "7.20.0", + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/plugin-block": "7.20.0", + "@milkdown/plugin-clipboard": "7.20.0", + "@milkdown/plugin-cursor": "7.20.0", + "@milkdown/plugin-history": "7.20.0", + "@milkdown/plugin-indent": "7.20.0", + "@milkdown/plugin-listener": "7.20.0", + "@milkdown/plugin-slash": "7.20.0", + "@milkdown/plugin-tooltip": "7.20.0", + "@milkdown/plugin-trailing": "7.20.0", + "@milkdown/plugin-upload": "7.20.0", + "@milkdown/preset-commonmark": "7.20.0", + "@milkdown/preset-gfm": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/transformer": "7.20.0", + "@milkdown/utils": "7.20.0" + } + }, + "node_modules/@milkdown/plugin-block": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-block/-/plugin-block-7.20.0.tgz", + "integrity": "sha512-jIXfzJ8Zje+6+9ZwQuVmNeYE8KfzqL9YJ/YdMvWQIEiKhy2x9pZMAkkufgmUlq1aouxOV+gk5fX+ovxzEzfSrA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.5.1", + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0", + "@types/lodash-es": "^4.17.12", + "lodash-es": "^4.17.21" + } + }, + "node_modules/@milkdown/plugin-clipboard": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-clipboard/-/plugin-clipboard-7.20.0.tgz", + "integrity": "sha512-PyokNvwgWcO6/I/0LxDRnATpnxvs5upFRlp6eO8PhjwBFZftCIU6D15Wg4JAxwW7Y0NyTWfViWjc9TwiBd6KOQ==", + "license": "MIT", + "dependencies": { + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0" + } + }, + "node_modules/@milkdown/plugin-cursor": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-cursor/-/plugin-cursor-7.20.0.tgz", + "integrity": "sha512-goCPwUARBzGV6Hvnr3P57Bj5TnyFjYIfDFLvgWTIlsm/dR2Wr4Syy4HDOtaKO9YL/VtZ8gtiZVgeo0vhc4CzMA==", + "license": "MIT", + "dependencies": { + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0", + "prosemirror-drop-indicator": "^0.1.0" + } + }, + "node_modules/@milkdown/plugin-history": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-history/-/plugin-history-7.20.0.tgz", + "integrity": "sha512-lqOYQBrxKj4px/i0Cav3zRkCArwnkv8o7fGMh3NtnUXMLSE7/xojK5GFPS4EaS/UKK7/+i1oV2+HRA6+6Ezy7w==", + "license": "MIT", + "dependencies": { + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0" + } + }, + "node_modules/@milkdown/plugin-indent": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-indent/-/plugin-indent-7.20.0.tgz", + "integrity": "sha512-KfdIztQMuHv4Rx1JmSQe2vooN4+Zm7MhjQkNolGyiI7BPZbu855hVIC/s96x3Dk04tkbb+M/i9MJhxCazxfd6Q==", + "license": "MIT", + "dependencies": { + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0" + } + }, + "node_modules/@milkdown/plugin-listener": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-listener/-/plugin-listener-7.20.0.tgz", + "integrity": "sha512-Sj+B63JfM3NVVS3uGXTPkoz8xx8MQYrR28pI9AaqX5q60tvCvOJw9E1ODvSsBEjeqnN4kablDthIugLlBhOlwQ==", + "license": "MIT", + "dependencies": { + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@types/lodash-es": "^4.17.12", + "lodash-es": "^4.17.21" + } + }, + "node_modules/@milkdown/plugin-slash": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-slash/-/plugin-slash-7.20.0.tgz", + "integrity": "sha512-Qm3/ZxkGYd5XN+J/X91lGGu7SBzuQBOTOLjuJdg4qDBZmdEHlGojB+5BhCSAMB3WGyCpQQGbSqKOelUrXtj68w==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.5.1", + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0", + "@types/lodash-es": "^4.17.12", + "lodash-es": "^4.17.21" + } + }, + "node_modules/@milkdown/plugin-tooltip": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-tooltip/-/plugin-tooltip-7.20.0.tgz", + "integrity": "sha512-BVaXorpmA6ZAS3+xv0rgrtjV1h2K39G5Z9Wun4RxT1YXJTTbzIuFQ3hwBAGLjLMwTsosp7YhRLaMJJAC0jEY5Q==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.5.1", + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0", + "@types/lodash-es": "^4.17.12", + "lodash-es": "^4.17.21" + } + }, + "node_modules/@milkdown/plugin-trailing": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-trailing/-/plugin-trailing-7.20.0.tgz", + "integrity": "sha512-AxDeMSAZfj0Er7RYLvLRf6FKdQtLVmotxML6Se6zgqIa++bFeIXCU22/FC+9r/6d1eUtraTva9ez5K2qPy8qig==", + "license": "MIT", + "dependencies": { + "@milkdown/ctx": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0" + } + }, + "node_modules/@milkdown/plugin-upload": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/plugin-upload/-/plugin-upload-7.20.0.tgz", + "integrity": "sha512-g3UQrD2zfpm86r3BcBDfOdEAyQHhay1nf5wUQgNf4zn6IgRttfEF8tosQsL1B/WBnZB05hH5scLWo4DR2bFhUw==", + "license": "MIT", + "dependencies": { + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/exception": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/utils": "7.20.0" + } + }, + "node_modules/@milkdown/preset-commonmark": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/preset-commonmark/-/preset-commonmark-7.20.0.tgz", + "integrity": "sha512-+mPcONXfdjaXdx8JMxDIOT3PWHfy5vewK8iY8j8bUiYD8Iw7YfyWTUh9JHOf4vmOpiKGCJd7+iz7e93u95bQRw==", + "license": "MIT", + "dependencies": { + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/exception": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/transformer": "7.20.0", + "@milkdown/utils": "7.20.0", + "remark-inline-links": "^7.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@milkdown/preset-gfm": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/preset-gfm/-/preset-gfm-7.20.0.tgz", + "integrity": "sha512-ulErTWDqrGYYqto4kQO9dPTMRp+q44pdRTPW4MTeiSO7eJ6JIBUKSqtCm1zf7MX6nZPaPhuscmg5CU2moXOwxQ==", + "license": "MIT", + "dependencies": { + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/exception": "7.20.0", + "@milkdown/preset-commonmark": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/transformer": "7.20.0", + "@milkdown/utils": "7.20.0", + "prosemirror-safari-ime-span": "^1.0.1", + "remark-gfm": "^4.0.1" + } + }, + "node_modules/@milkdown/prose": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/prose/-/prose-7.20.0.tgz", + "integrity": "sha512-Qe6jmKcXsjOfpk8duDFdkLCEo5044L8HSyKVn7ewAe7XJJPUM6bPQaP130UAznq75/+TiKxFCzurcrBO3LzNRg==", + "license": "MIT", + "dependencies": { + "@milkdown/exception": "7.20.0", + "prosemirror-changeset": "^2.3.1", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.0", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.1", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.3" + } + }, + "node_modules/@milkdown/react": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/react/-/react-7.20.0.tgz", + "integrity": "sha512-uuMuMfTmp2SZJtmKhX+tmVkJNasKnzxlYoHtUwjxuZcy90cWQVYpGXnmwpFgjcXY38lMLsAOLx2jjXrqe7ZOuQ==", + "license": "MIT", + "dependencies": { + "@milkdown/crepe": "7.20.0", + "@milkdown/kit": "7.20.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@milkdown/transformer": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/transformer/-/transformer-7.20.0.tgz", + "integrity": "sha512-h7KGFr1o5AYwc+hEfnA3Dldo4jRrYOB/7KExaqelcjUz++KYI/9LXMOsV7CpgjtLI3Xtf2IIRTZND1+p2nsOaw==", + "license": "MIT", + "dependencies": { + "@milkdown/exception": "7.20.0", + "@milkdown/prose": "7.20.0", + "remark": "^15.0.1", + "unified": "^11.0.3" + } + }, + "node_modules/@milkdown/utils": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@milkdown/utils/-/utils-7.20.0.tgz", + "integrity": "sha512-ciEhtLKhIW/Kaz/NRE5DUXVoMCdenn7S4mClrO7sZ/nXtmObnk3okJzSDnamQoDOcLOIbpOu1V3E1Btkvc5x9w==", + "license": "MIT", + "dependencies": { + "@milkdown/core": "7.20.0", + "@milkdown/ctx": "7.20.0", + "@milkdown/exception": "7.20.0", + "@milkdown/prose": "7.20.0", + "@milkdown/transformer": "7.20.0", + "nanoid": "^5.0.9" + } + }, + "node_modules/@milkdown/utils/node_modules/nanoid": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.11.tgz", + "integrity": "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@ocavue/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@ocavue/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-8W3q1hxx9qFdrYgPtbElllG/tqYkO/dMhlRUiqasO0SuDFTj78azSQjhIrBTFWxlBPPsSZN6zXYHmb3RwN2Jtg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ocavue" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -1003,19 +1864,16 @@ } } }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -1032,11 +1890,14 @@ } } }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -1047,38 +1908,79 @@ } } }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" - }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -1090,13 +1992,61 @@ } } }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", - "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.4" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", @@ -1113,13 +2063,30 @@ } } }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.4" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", @@ -1136,6 +2103,24 @@ } } }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", @@ -1822,60 +2807,48 @@ "win32" ] }, - "node_modules/@tailwindcss/forms": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", - "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", - "license": "MIT", - "dependencies": { - "mini-svg-data-uri": "^1.2.3" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1" - } - }, "node_modules/@tailwindcss/node": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz", - "integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", + "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.21.0", "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.19", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.16" + "tailwindcss": "4.3.0" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz", - "integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", + "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.16", - "@tailwindcss/oxide-darwin-arm64": "4.1.16", - "@tailwindcss/oxide-darwin-x64": "4.1.16", - "@tailwindcss/oxide-freebsd-x64": "4.1.16", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", - "@tailwindcss/oxide-linux-x64-musl": "4.1.16", - "@tailwindcss/oxide-wasm32-wasi": "4.1.16", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" + "@tailwindcss/oxide-android-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-arm64": "4.3.0", + "@tailwindcss/oxide-darwin-x64": "4.3.0", + "@tailwindcss/oxide-freebsd-x64": "4.3.0", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", + "@tailwindcss/oxide-linux-x64-musl": "4.3.0", + "@tailwindcss/oxide-wasm32-wasi": "4.3.0", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz", - "integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", + "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", "cpu": [ "arm64" ], @@ -1885,13 +2858,13 @@ "android" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz", - "integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", + "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", "cpu": [ "arm64" ], @@ -1901,13 +2874,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz", - "integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", + "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", "cpu": [ "x64" ], @@ -1917,13 +2890,13 @@ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz", - "integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", + "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", "cpu": [ "x64" ], @@ -1933,13 +2906,13 @@ "freebsd" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz", - "integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", + "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", "cpu": [ "arm" ], @@ -1949,13 +2922,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz", - "integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", + "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", "cpu": [ "arm64" ], @@ -1965,13 +2938,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz", - "integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", + "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", "cpu": [ "arm64" ], @@ -1981,13 +2954,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz", - "integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", + "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", "cpu": [ "x64" ], @@ -1997,13 +2970,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz", - "integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", + "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", "cpu": [ "x64" ], @@ -2013,13 +2986,13 @@ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz", - "integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", + "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2034,21 +3007,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.0.7", + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz", - "integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", + "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", "cpu": [ "arm64" ], @@ -2058,13 +3031,13 @@ "win32" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz", - "integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", + "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", "cpu": [ "x64" ], @@ -2074,33 +3047,21 @@ "win32" ], "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + "node": ">= 20" } }, "node_modules/@tailwindcss/vite": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.16.tgz", - "integrity": "sha512-bbguNBcDxsRmi9nnlWJxhfDWamY3lmcyACHcdO1crxfzuLpOhHLLtEIN/nCbbAtj5rchUgQD17QVAKi1f7IsKg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.16", - "@tailwindcss/oxide": "4.1.16", - "tailwindcss": "4.1.16" + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" }, "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" + "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "node_modules/@types/babel__core": { @@ -2144,12 +3105,36 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", @@ -2165,6 +3150,21 @@ "@types/lodash": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.6.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", @@ -2193,6 +3193,19 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.0.tgz", @@ -2213,6 +3226,106 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vue/compiler-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz", + "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.34", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz", + "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz", + "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.34", + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.14", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz", + "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz", + "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz", + "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz", + "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/runtime-core": "3.5.34", + "@vue/shared": "3.5.34", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz", + "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "vue": "3.5.34" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz", + "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==", + "license": "MIT" + }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", @@ -2232,16 +3345,26 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.8.25", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", @@ -2346,6 +3469,26 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -2367,6 +3510,21 @@ "node": ">=6" } }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2379,28 +3537,31 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "license": "MIT" }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/debug": { @@ -2420,8 +3581,21 @@ } } }, - "node_modules/delayed-stream": { - "version": "1.0.0", + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", @@ -2429,6 +3603,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2444,6 +3627,28 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dompurify": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", + "integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2465,18 +3670,30 @@ "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz", + "integrity": "sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2572,6 +3789,30 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -2613,9 +3854,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", @@ -2829,6 +4070,18 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -2868,10 +4121,26 @@ "node": ">=6" } }, + "node_modules/katex": { + "version": "0.16.45", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz", + "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -2884,23 +4153,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", "cpu": [ "arm64" ], @@ -2918,9 +4187,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -2938,9 +4207,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", "cpu": [ "x64" ], @@ -2958,9 +4227,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", "cpu": [ "x64" ], @@ -2978,9 +4247,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", "cpu": [ "arm" ], @@ -2998,9 +4267,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", "cpu": [ "arm64" ], @@ -3018,9 +4287,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", "cpu": [ "arm64" ], @@ -3038,9 +4307,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", "cpu": [ "x64" ], @@ -3058,9 +4327,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", "cpu": [ "x64" ], @@ -3078,9 +4347,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", "cpu": [ "arm64" ], @@ -3098,9 +4367,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", "cpu": [ "x64" ], @@ -3123,6 +4392,16 @@ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3133,9 +4412,9 @@ } }, "node_modules/lucide-react": { - "version": "0.552.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.552.0.tgz", - "integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz", + "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3150,25 +4429,840 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", - "engines": { - "node": ">= 0.4" + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">= 8" + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -3204,15 +5298,6 @@ "node": ">= 0.6" } }, - "node_modules/mini-svg-data-uri": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", - "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==", - "license": "MIT", - "bin": { - "mini-svg-data-uri": "cli.js" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3255,6 +5340,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3275,9 +5366,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "funding": [ { "type": "opencollective", @@ -3302,17 +5393,196 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "node_modules/prosemirror-changeset": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.1.tgz", + "integrity": "sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-drop-indicator": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/prosemirror-drop-indicator/-/prosemirror-drop-indicator-0.1.3.tgz", + "integrity": "sha512-fJV6G2tHIVXZLUuc60fS9ly1/GuGOlAZUm67S1El+kGFUYh27Hyv6hcGx3rrJ+Q/JZL5jnyAibIZYYWpPqE45g==", "license": "MIT", "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" + "@ocavue/utils": "^1.0.0", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-view": "^1.41.3" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/sponsors/ocavue" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-safari-ime-span": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/prosemirror-safari-ime-span/-/prosemirror-safari-ime-span-1.0.2.tgz", + "integrity": "sha512-QJqD8s1zE/CuK56kDsUhndh5hiHh/gFnAuPOA9ytva2s85/ZEt2tNWeALTJN48DtWghSKOmiBsvVn2OlnJ5H2w==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.4.3", + "prosemirror-view": "^1.33.8" + }, + "funding": { + "url": "https://github.com/sponsors/ocavue" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", + "integrity": "sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.8", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.8.tgz", + "integrity": "sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/prosemirror-virtual-cursor": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/prosemirror-virtual-cursor/-/prosemirror-virtual-cursor-0.4.2.tgz", + "integrity": "sha512-pUMKnIuOhhnMcgIJUjhIQTVJruBEGxfMBVQSrK0g2qhGPDm1i12KdsVaFw15dYk+29tZcxjMeR7P5VDKwmbwJg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ocavue" + }, + "peerDependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + }, + "peerDependenciesMeta": { + "prosemirror-model": { + "optional": true + }, + "prosemirror-state": { + "optional": true + }, + "prosemirror-view": { + "optional": true + } } }, "node_modules/proxy-from-env": { @@ -3459,6 +5729,102 @@ } } }, + "node_modules/remark": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", + "integrity": "sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-inline-links": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/remark-inline-links/-/remark-inline-links-7.0.0.tgz", + "integrity": "sha512-4uj1pPM+F495ySZhTIB6ay2oSkTsKgmYaKk/q5HIdhX2fuyLEegpjWa0VdJRJ01sgOqAFo7MBKdDUejIYBMVMQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-definitions": "^6.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3514,6 +5880,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3634,10 +6006,16 @@ "node": ">=0.10.0" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/tailwind-merge": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", - "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", "license": "MIT", "funding": { "type": "github", @@ -3645,15 +6023,15 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", - "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", "license": "MIT" }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" @@ -3721,22 +6099,22 @@ "node": ">=8.0" } }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/tw-animate-css": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", - "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Wombosvideo" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -3757,6 +6135,94 @@ "devOptional": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -3830,11 +6296,33 @@ } } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, "node_modules/vite": { "version": "7.3.2", @@ -3953,11 +6441,48 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vue": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz", + "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-sfc": "3.5.34", + "@vue/runtime-dom": "3.5.34", + "@vue/server-renderer": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "license": "ISC" + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index a221406..b1f4a03 100644 --- a/package.json +++ b/package.json @@ -4,33 +4,34 @@ "type": "module", "devDependencies": { "@types/node": "^25.6.0", - "tw-animate-css": "^1.4.0", "vite": "^7.2.0", "vite-plugin-ruby": "^5.1.1" }, "dependencies": { "@inertiajs/react": "^2.2.15", + "@milkdown/core": "^7.20.0", + "@milkdown/crepe": "^7.20.0", + "@milkdown/react": "^7.20.0", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-slot": "^1.2.4", - "@tailwindcss/forms": "^0.5.10", - "@tailwindcss/typography": "^0.5.19", - "@tailwindcss/vite": "^4.1.16", + "@tailwindcss/vite": "^4.3.0", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@vitejs/plugin-react": "^5.1.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "lucide-react": "^0.552.0", + "lucide-react": "^1.14.0", "react": "^19.2.0", "react-dom": "^19.2.0", - "tailwind-merge": "^3.3.1", - "tailwindcss": "^4.1.16", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.3.0", "typescript": "^5.9.3" }, "scripts": { - "check": "tsc -p tsconfig.app.json && tsc -p tsconfig.node.json", - "build:ssr": "vite build --ssr app/javascript/entrypoints/ssr.tsx --outDir public/vite-ssr --emptyOutDir", - "ssr": "node public/vite-ssr/ssr.js" + "check": "tsc -p tsconfig.app.json && tsc -p tsconfig.node.json" + }, + "engines": { + "node": ">=22.12.0" } } diff --git a/test/controllers/admin/users_controller_test.rb b/test/controllers/admin/users_controller_test.rb new file mode 100644 index 0000000..7cd0c92 --- /dev/null +++ b/test/controllers/admin/users_controller_test.rb @@ -0,0 +1,56 @@ +require "test_helper" + +class Admin::UsersControllerTest < ActionDispatch::IntegrationTest + setup do + @admin = users(:admin) + @user = users(:one) + @password = "password" + end + + test "unauthenticated users are redirected to login" do + get admin_users_path + assert_redirected_to login_path + end + + test "non-admin users are redirected to root with an alert" do + log_in_as(@user) + get admin_users_path + assert_redirected_to root_path + assert_equal "You don't have access to that page.", flash[:alert] + end + + test "admin users can list users" do + log_in_as(@admin) + get admin_users_path + assert_response :success + end + + test "admin users can view a single user" do + log_in_as(@admin) + get admin_user_path(@user) + assert_response :success + end + + test "non-admin users cannot view a single user" do + log_in_as(@user) + get admin_user_path(@user) + assert_redirected_to root_path + end + + test "non-admin users cannot reach admin design system" do + log_in_as(@user) + get admin_design_system_path + assert_redirected_to root_path + end + + test "admin users can reach admin design system" do + log_in_as(@admin) + get admin_design_system_path + assert_response :success + end + + private + def log_in_as(user) + post login_path, params: { email: user.email, password: @password } + end +end diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 4ffcc8b..dd6a943 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -7,3 +7,8 @@ one: two: email: two@example.com password_digest: <%= password_digest %> + +admin: + email: admin@example.com + password_digest: <%= password_digest %> + admin: true diff --git a/test/integration/ssr_smoke_test.rb b/test/integration/ssr_smoke_test.rb new file mode 100644 index 0000000..60eee28 --- /dev/null +++ b/test/integration/ssr_smoke_test.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require "test_helper" +require "net/http" +require "json" +require "socket" + +# Boots the Node SSR server against the built bundle, posts a page payload +# to /render, and asserts the result contains real markup. Catches drift +# between client and SSR entrypoints, broken `noExternal` resolution, and +# browser-globals-at-top-level mistakes that only surface during SSR. +class SsrSmokeTest < ActiveSupport::TestCase + PORT = 13714 + SSR_BUNDLE = Rails.root.join("public/vite-ssr/ssr.js") + STARTUP_TIMEOUT = 15 + + setup do + unless Rails.root.join("node_modules").exist? + flunk "node_modules missing — run `npm install` before running SSR tests" + end + + if port_in_use? + flunk "Port #{PORT} is already in use; stop any running `bin/dev-ssr` " \ + "or `npm run ssr` before running this test" + end + + unless system("bin/vite build --ssr", chdir: Rails.root.to_s) + flunk "Failed to build SSR bundle — check `bin/vite build --ssr` output" + end + + @ssr_pid = Process.spawn("node", SSR_BUNDLE.to_s, out: File::NULL, err: File::NULL) + + deadline = Time.now + STARTUP_TIMEOUT + until port_in_use? + flunk "SSR server failed to start within #{STARTUP_TIMEOUT}s" if Time.now > deadline + sleep 0.1 + end + end + + teardown do + next unless @ssr_pid + Process.kill("TERM", @ssr_pid) + Process.wait(@ssr_pid) + rescue Errno::ESRCH, Errno::ECHILD + nil + end + + test "SSR pipeline renders a known page with real markup and head tags" do + result = render_page("Home") + + assert_kind_of String, result["body"] + refute_empty result["body"], "SSR returned an empty body" + assert_includes result["body"], "Hello world", + "Expected SSR-rendered Home page to include 'Hello world'; got: #{result["body"][0, 300]}" + + head = Array(result["head"]).join + assert_includes head, " tag" + end + + private + + def port_in_use? + TCPSocket.new("localhost", PORT).close + true + rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL + false + end + + def render_page(component, props = {}) + body = { + component: component, + props: { + current_user: nil, + flash: { notice: nil, alert: nil }, + errors: {}, + **props + }, + url: "/", + version: "test", + encryptHistory: false, + clearHistory: false + }.to_json + + res = Net::HTTP.post( + URI("http://localhost:#{PORT}/render"), + body, + "Content-Type" => "application/json" + ) + + flunk "SSR /render returned #{res.code}: #{res.body[0, 300]}" unless res.is_a?(Net::HTTPSuccess) + + JSON.parse(res.body) + end +end diff --git a/vite.config.ts b/vite.config.ts index 2f5c56a..3d4b065 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -35,10 +35,10 @@ export default defineConfig({ '@': fileURLToPath(new URL('./app/frontend', import.meta.url)), }, }, - // SSR bundle is built from app/javascript/entrypoints/ssr.tsx via - // `vite build --ssr` (see package.json `build:ssr`). noExternal: true - // bundles every dependency into the SSR output so the Node process - // can boot without resolving anything from node_modules at runtime. + // SSR. `bin/vite build --ssr` bundles app/javascript/ssr/ssr.tsx (the + // vite-plugin-ruby default `ssrEntrypoint`) into public/vite-ssr/ssr.js. + // noExternal: true bundles every dependency into the output so the Node + // process can boot without resolving anything from node_modules. ssr: { noExternal: true, },