diff --git a/.gitignore b/.gitignore index 011e869..4ce6898 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ dist/ Thumbs.db # playright +.playwright-mcp/ +browser-extension/dist-playground/ browser-extension/playwright-report/ browser-extension/playwright/ -browser-extension/test-results/ \ No newline at end of file +browser-extension/test-results/ diff --git a/browser-extension/README.md b/browser-extension/README.md index cb2641f..79275aa 100644 --- a/browser-extension/README.md +++ b/browser-extension/README.md @@ -33,15 +33,15 @@ This is a [WXT](https://wxt.dev/)-based browser extension that ### Entry points -- `src/entrypoints/content.ts` - injected into every webpage -- `src/entrypoints/background.ts` - service worker that manages state and handles messages -- `src/entrypoints/popup` - html/css/ts which opens when the extension's button gets clicked +- [`src/entrypoints/content.ts`](src/entrypoints/content.ts) - injected into every webpage +- [`src/entrypoints/background.ts`](src/entrypoints/background.ts) - service worker that manages state and handles messages +- [`src/entrypoints/popup/popup.tsx`](src/entrypoints/popup/popup.tsx) - popup (html/css/tsx) with shadcn/ui table components ```mermaid graph TD Content[Content Script
content.ts] Background[Background Script
background.ts] - Popup[Popup Script
popup/main.ts] + Popup[Popup Script
popup/popup.tsx] Content -->|ENHANCED/DESTROYED
CommentEvent| Background Popup -->|GET_OPEN_SPOTS
SWITCH_TO_TAB| Background @@ -60,22 +60,20 @@ graph TD class TextArea,UI ui ``` -### Architecture +Every time a `textarea` shows up on a page, on initial load or later on, it gets passed to a list of `CommentEnhancer`s. Each one gets a turn to say "I can enhance this box!". They show that they can enhance it by returning something non-null in the method `tryToEnhance(textarea: HTMLTextAreaElement): Spot | null`. Later on, that same `Spot` data will be used by the `tableRow(spot: Spot): ReactNode` method to create React components for rich formatting in the popup table. -Every time a `textarea` shows up on a page, on initial load or later on, it gets passed to a list of `CommentEnhancer`s. Each one gets a turn to say "I can enhance this box!". They show that they can enhance it by returning a [`CommentSpot`, `Overtype`]. +Those `Spot` values get bundled up with the `HTMLTextAreaElement` itself into an `EnhancedTextarea`, which gets added to the `TextareaRegistry`. At some interval, draft edits get saved by the browser extension. -Those values get bundled up with the `HTMLTextAreaElement` itself into an `EnhancedTextarea`, which gets added to the `TextareaRegistry`. At some interval, draft edits will get saved by the browser extension (TODO). - -When the `textarea` gets removed from the page, the `TextareaRegistry` is notified so that the `CommentSpot` can be marked as abandoned or submitted as appropriate (TODO). +When the `textarea` gets removed from the page, the `TextareaRegistry` is notified so that the `CommentSpot` can be marked as abandoned or submitted as appropriate. ## Testing -In `tests/har` there are various `.har` files. These are complete recordings of a single page load. - -- `pnpm run har:view` and you can see the recordings, with or without our browser extension. +- `npm run playground` gives you a test environment where you can tinker with the popup with various test data, supports hot reload +- `npm run har:view` gives you recordings of various web pages which you can see with and without enhancement by the browser extension ### Recording new HAR files +- the har recordings live in `tests/har`, they are complete recordings of the network requests of a single page load - you can add or change URLs in `tests/har-index.ts` - `npx playwright codegen https://github.com/login --save-storage=playwright/.auth/gh.json` will store new auth tokens - login manually, then close the browser diff --git a/browser-extension/biome.json b/browser-extension/biome.json index d4b34d1..54b9867 100644 --- a/browser-extension/biome.json +++ b/browser-extension/biome.json @@ -66,6 +66,7 @@ } }, "noExplicitAny": "off", + "noUnknownAtRules": "off", "noVar": "error" } } diff --git a/browser-extension/components.json b/browser-extension/components.json new file mode 100644 index 0000000..cd33c15 --- /dev/null +++ b/browser-extension/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/styles/globals.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/browser-extension/package.json b/browser-extension/package.json index 128aa79..e2eabb0 100644 --- a/browser-extension/package.json +++ b/browser-extension/package.json @@ -1,25 +1,40 @@ { "author": "DiffPlug", "dependencies": { + "@primer/octicons-react": "^19.18.0", + "@radix-ui/react-slot": "^1.2.3", + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", "@wxt-dev/webextension-polyfill": "^1.0.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "highlight.js": "^11.11.1", + "lucide-react": "^0.543.0", "overtype": "workspace:*", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "tailwind-merge": "^3.3.1", "webextension-polyfill": "^0.12.0" }, "description": "Syntax highlighting and autosave for comments on GitHub (and other other markdown-friendly websites).", "devDependencies": { "@biomejs/biome": "^2.1.2", "@playwright/test": "^1.46.0", + "@tailwindcss/vite": "^4.1.13", "@testing-library/jest-dom": "^6.6.4", "@types/express": "^4.17.21", "@types/har-format": "^1.2.16", "@types/node": "^22.16.5", + "@vitejs/plugin-react": "^5.0.2", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", "express": "^4.19.2", "linkedom": "^0.18.12", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.13", "tsx": "^4.19.1", "typescript": "^5.8.3", + "vite": "^7.1.5", "vitest": "^3.2.4", "wxt": "^0.20.7" }, @@ -47,6 +62,7 @@ "dev:firefox": "wxt -b firefox", "postinstall": "wxt prepare", "test": "vitest run", + "playground": "vite --config vite.playground.config.ts", "har:record": "tsx tests/har-record.ts", "har:view": "tsx tests/har-view.ts" }, diff --git a/browser-extension/src/components/SpotRow.tsx b/browser-extension/src/components/SpotRow.tsx new file mode 100644 index 0000000..a394901 --- /dev/null +++ b/browser-extension/src/components/SpotRow.tsx @@ -0,0 +1,40 @@ +import { TableCell, TableRow } from '@/components/ui/table' +import type { CommentState } from '@/entrypoints/background' +import type { EnhancerRegistry } from '@/lib/registries' +import { cn } from '@/lib/utils' + +interface SpotRowProps { + commentState: CommentState + enhancerRegistry: EnhancerRegistry + onClick: () => void + className?: string + cellClassName?: string + errorClassName?: string +} + +export function SpotRow({ + commentState, + enhancerRegistry, + onClick, + className, + cellClassName = 'p-3', + errorClassName = 'text-red-500', +}: SpotRowProps) { + const enhancer = enhancerRegistry.enhancerFor(commentState.spot) + + if (!enhancer) { + return ( + + +
Unknown spot type: {commentState.spot.type}
+
+
+ ) + } + + return ( + + {enhancer.tableRow(commentState.spot)} + + ) +} diff --git a/browser-extension/src/components/SpotTable.tsx b/browser-extension/src/components/SpotTable.tsx new file mode 100644 index 0000000..33e26e7 --- /dev/null +++ b/browser-extension/src/components/SpotTable.tsx @@ -0,0 +1,78 @@ +import { Table, TableBody, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import type { CommentState } from '@/entrypoints/background' +import type { EnhancerRegistry } from '@/lib/registries' +import { SpotRow } from './SpotRow' + +interface SpotTableProps { + spots: CommentState[] + enhancerRegistry: EnhancerRegistry + onSpotClick: (spot: CommentState) => void + title?: string + description?: string + headerText?: string + className?: string + headerClassName?: string + rowClassName?: string + cellClassName?: string + emptyStateMessage?: string + showHeader?: boolean +} + +export function SpotTable({ + spots, + enhancerRegistry, + onSpotClick, + title, + description, + headerText = 'Comment Spots', + className, + headerClassName = 'p-3 font-medium text-muted-foreground', + rowClassName, + cellClassName, + emptyStateMessage = 'No comment spots available', + showHeader = true, +}: SpotTableProps) { + if (spots.length === 0) { + return
{emptyStateMessage}
+ } + + const tableContent = ( + + {showHeader && ( + + + {headerText} + + + )} + + {spots.map((spot) => ( + onSpotClick(spot)} + className={rowClassName || ''} + cellClassName={cellClassName || 'p-3'} + /> + ))} + +
+ ) + + if (title || description) { + return ( +
+ {(title || description) && ( +
+ {title &&

{title}

} + {description &&

{description}

} +
+ )} + {tableContent} +
+ ) + } + + return
{tableContent}
+} diff --git a/browser-extension/src/components/ui/button.tsx b/browser-extension/src/components/ui/button.tsx new file mode 100644 index 0000000..80f6691 --- /dev/null +++ b/browser-extension/src/components/ui/button.tsx @@ -0,0 +1,49 @@ +import { Slot } from '@radix-ui/react-slot' +import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + { + defaultVariants: { + size: 'default', + variant: 'default', + }, + variants: { + size: { + default: 'h-10 px-4 py-2', + icon: 'h-10 w-10', + lg: 'h-11 rounded-md px-8', + sm: 'h-9 rounded-md px-3', + }, + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + }, + }, + }, +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +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/browser-extension/src/components/ui/table.tsx b/browser-extension/src/components/ui/table.tsx new file mode 100644 index 0000000..e8548bf --- /dev/null +++ b/browser-extension/src/components/ui/table.tsx @@ -0,0 +1,91 @@ +import * as React from 'react' + +import { cn } from '@/lib/utils' + +const Table = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ + + ), +) +Table.displayName = 'Table' + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = 'TableHeader' + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = 'TableBody' + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0', className)} + {...props} + /> +)) +TableFooter.displayName = 'TableFooter' + +const TableRow = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +) +TableRow.displayName = 'TableRow' + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = 'TableHead' + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = 'TableCell' + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = 'TableCaption' + +export { Table, TableHeader, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableRow } diff --git a/browser-extension/src/entrypoints/background.ts b/browser-extension/src/entrypoints/background.ts index 3e071a1..3dcdf7d 100644 --- a/browser-extension/src/entrypoints/background.ts +++ b/browser-extension/src/entrypoints/background.ts @@ -1,12 +1,12 @@ -import type { CommentDraft, CommentEvent, CommentSpot } from '../lib/enhancer' -import type { GetOpenSpotsResponse, ToBackgroundMessage } from '../lib/messages' +import type { CommentDraft, CommentEvent, CommentSpot } from '@/lib/enhancer' +import type { GetOpenSpotsResponse, ToBackgroundMessage } from '@/lib/messages' import { CLOSE_MESSAGE_PORT, isContentToBackgroundMessage, isGetOpenSpotsMessage, isSwitchToTabMessage, KEEP_PORT_OPEN, -} from '../lib/messages' +} from '@/lib/messages' export interface Tab { tabId: number @@ -27,14 +27,13 @@ export function handleCommentEvent(message: CommentEvent, sender: any): boolean sender.tab?.windowId ) { if (message.type === 'ENHANCED') { - const tab: Tab = { - tabId: sender.tab.id, - windowId: sender.tab.windowId, - } const commentState: CommentState = { drafts: [], spot: message.spot, - tab, + tab: { + tabId: sender.tab.id, + windowId: sender.tab.windowId, + }, } openSpots.set(message.spot.unique_key, commentState) } else if (message.type === 'DESTROYED') { @@ -52,10 +51,7 @@ export function handlePopupMessage( sendResponse: (response: any) => void, ): typeof CLOSE_MESSAGE_PORT | typeof KEEP_PORT_OPEN { if (isGetOpenSpotsMessage(message)) { - const spots: CommentState[] = [] - for (const [, commentState] of openSpots) { - spots.push(commentState) - } + const spots: CommentState[] = Array.from(openSpots.values()) const response: GetOpenSpotsResponse = { spots } sendResponse(response) return KEEP_PORT_OPEN diff --git a/browser-extension/src/entrypoints/content.ts b/browser-extension/src/entrypoints/content.ts index 83de338..c005929 100644 --- a/browser-extension/src/entrypoints/content.ts +++ b/browser-extension/src/entrypoints/content.ts @@ -1,8 +1,8 @@ -import { CONFIG, type ModeType } from '../lib/config' -import type { CommentEvent, CommentSpot } from '../lib/enhancer' -import { logger } from '../lib/logger' -import { EnhancerRegistry, TextareaRegistry } from '../lib/registries' -import { githubPrNewCommentContentScript } from '../playgrounds/github-playground' +import { CONFIG, type ModeType } from '@/lib/config' +import type { CommentEvent, CommentSpot } from '@/lib/enhancer' +import { logger } from '@/lib/logger' +import { EnhancerRegistry, TextareaRegistry } from '@/lib/registries' +import { githubPrNewCommentContentScript } from '@/playgrounds/github-playground' const enhancers = new EnhancerRegistry() const enhancedTextareas = new TextareaRegistry() diff --git a/browser-extension/src/entrypoints/popup/index.html b/browser-extension/src/entrypoints/popup/index.html index af3b409..66aa778 100644 --- a/browser-extension/src/entrypoints/popup/index.html +++ b/browser-extension/src/entrypoints/popup/index.html @@ -8,6 +8,6 @@
- + \ No newline at end of file diff --git a/browser-extension/src/entrypoints/popup/main.ts b/browser-extension/src/entrypoints/popup/main.ts deleted file mode 100644 index 4ccea9a..0000000 --- a/browser-extension/src/entrypoints/popup/main.ts +++ /dev/null @@ -1,97 +0,0 @@ -import './style.css' -import { logger } from '../../lib/logger' -import type { - GetOpenSpotsMessage, - GetOpenSpotsResponse, - SwitchToTabMessage, -} from '../../lib/messages' -import { EnhancerRegistry } from '../../lib/registries' -import type { CommentState } from '../background' - -// Test basic DOM access -try { - const app = document.getElementById('app')! - logger.debug('Found app element:', app) - app.innerHTML = '
Script is running...
' -} catch (error) { - logger.error('Error accessing DOM:', error) -} - -const enhancers = new EnhancerRegistry() - -async function getOpenSpots(): Promise { - logger.debug('Sending message to background script...') - try { - const message: GetOpenSpotsMessage = { type: 'GET_OPEN_SPOTS' } - const response = (await browser.runtime.sendMessage(message)) as GetOpenSpotsResponse - logger.debug('Received response:', response) - return response.spots || [] - } catch (error) { - logger.error('Error sending message to background:', error) - return [] - } -} - -function switchToTab(tabId: number, windowId: number): void { - // Send message to background script to handle tab switching - // This avoids the popup context being destroyed before completion - const message: SwitchToTabMessage = { - tabId, - type: 'SWITCH_TO_TAB', - windowId, - } - browser.runtime.sendMessage(message) - window.close() -} - -function createSpotElement(commentState: CommentState): HTMLElement { - const item = document.createElement('div') - item.className = 'spot-item' - - logger.debug('Creating spot element for:', commentState.spot) - const enhancer = enhancers.enhancerFor(commentState.spot) - if (!enhancer) { - logger.error('No enhancer found for:', commentState.spot) - logger.error('Only have enhancers for:', enhancers.byType) - } - - const title = document.createElement('div') - title.className = 'spot-title' - title.textContent = enhancer.tableTitle(commentState.spot) - item.appendChild(title) - item.addEventListener('click', () => { - switchToTab(commentState.tab.tabId, commentState.tab.windowId) - }) - return item -} - -async function renderOpenSpots(): Promise { - logger.debug('renderOpenSpots called') - const app = document.getElementById('app')! - const spots = await getOpenSpots() - logger.debug('Got spots:', spots) - - if (spots.length === 0) { - app.innerHTML = '
No open comment spots
' - return - } - - const header = document.createElement('h2') - header.textContent = 'Open Comment Spots' - app.appendChild(header) - - const list = document.createElement('div') - list.className = 'spots-list' - - spots.forEach((spot) => { - list.appendChild(createSpotElement(spot)) - }) - - app.appendChild(list) -} - -renderOpenSpots().catch((error) => { - logger.error('Error in renderOpenSpots:', error) - const app = document.getElementById('app')! - app.innerHTML = `
Error loading spots: ${error.message}
` -}) diff --git a/browser-extension/src/entrypoints/popup/popup.tsx b/browser-extension/src/entrypoints/popup/popup.tsx new file mode 100644 index 0000000..fa1cea2 --- /dev/null +++ b/browser-extension/src/entrypoints/popup/popup.tsx @@ -0,0 +1,89 @@ +import './style.css' +import React from 'react' +import { createRoot } from 'react-dom/client' +import { SpotTable } from '@/components/SpotTable' +import type { CommentState } from '@/entrypoints/background' +import { logger } from '@/lib/logger' +import type { GetOpenSpotsMessage, GetOpenSpotsResponse, SwitchToTabMessage } from '@/lib/messages' +import { EnhancerRegistry } from '@/lib/registries' + +async function getOpenSpots(): Promise { + logger.debug('Sending message to background script...') + try { + const message: GetOpenSpotsMessage = { type: 'GET_OPEN_SPOTS' } + const response = (await browser.runtime.sendMessage(message)) as GetOpenSpotsResponse + logger.debug('Received response:', response) + return response.spots || [] + } catch (error) { + logger.error('Error sending message to background:', error) + return [] + } +} + +function switchToTab(tabId: number, windowId: number): void { + const message: SwitchToTabMessage = { + tabId, + type: 'SWITCH_TO_TAB', + windowId, + } + browser.runtime.sendMessage(message) + window.close() +} + +const enhancers = new EnhancerRegistry() + +function PopupApp() { + const [spots, setSpots] = React.useState([]) + const [isLoading, setIsLoading] = React.useState(true) + + React.useEffect(() => { + const loadSpots = async () => { + try { + const openSpots = await getOpenSpots() + setSpots(openSpots) + } catch (error) { + logger.error('Error loading spots:', error) + } finally { + setIsLoading(false) + } + } + + loadSpots() + }, []) + + if (isLoading) { + return
Loading...
+ } + + const handleSpotClick = (spot: CommentState) => { + switchToTab(spot.tab.tabId, spot.tab.windowId) + } + + return ( +
+

Open Comment Spots

+ +
+ +
+
+ ) +} + +// Initialize React app +const app = document.getElementById('app') +if (app) { + const root = createRoot(app) + root.render() +} else { + logger.error('App element not found') +} diff --git a/browser-extension/src/entrypoints/popup/style.css b/browser-extension/src/entrypoints/popup/style.css index 0d63390..8e9d480 100644 --- a/browser-extension/src/entrypoints/popup/style.css +++ b/browser-extension/src/entrypoints/popup/style.css @@ -1,53 +1,5 @@ -body { - width: 300px; - padding: 15px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - font-size: 14px; - line-height: 1.4; - margin: 0; -} +@import url("../../styles/popup-frame.css"); -h2 { - margin: 0 0 15px 0; - font-size: 16px; - font-weight: 600; - color: #333; -} - -.spots-list { - display: flex; - flex-direction: column; - gap: 8px; -} - -.spot-item { - padding: 10px; - border: 1px solid #e0e0e0; - border-radius: 6px; - cursor: pointer; - transition: all 0.2s ease; - background: white; -} - -.spot-item:hover { - background: #f5f5f5; - border-color: #d0d0d0; - transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.spot-title { - font-weight: 500; - color: #333; - margin-bottom: 4px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.no-spots { - text-align: center; - color: #666; - padding: 40px 20px; - font-style: italic; -} +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/browser-extension/src/lib/enhancer.ts b/browser-extension/src/lib/enhancer.ts index 0895454..06b294f 100644 --- a/browser-extension/src/lib/enhancer.ts +++ b/browser-extension/src/lib/enhancer.ts @@ -1,4 +1,5 @@ import type { OverTypeInstance } from 'overtype' +import type { ReactNode } from 'react' /** * Stores enough info about the location of a draft to: @@ -40,7 +41,6 @@ export interface CommentEnhancer { * exactly once since pageload before this gets called. */ enhance(textarea: HTMLTextAreaElement, spot: Spot): OverTypeInstance - - tableIcon(spot: Spot): string - tableTitle(spot: Spot): string + /** Returns a ReactNode which will be displayed in the table row. */ + tableRow(spot: Spot): ReactNode } diff --git a/browser-extension/src/lib/enhancers/github/githubIssueAddComment.ts b/browser-extension/src/lib/enhancers/github/githubIssueAddComment.tsx similarity index 78% rename from browser-extension/src/lib/enhancers/github/githubIssueAddComment.ts rename to browser-extension/src/lib/enhancers/github/githubIssueAddComment.tsx index bfdc02f..b35f8f9 100644 --- a/browser-extension/src/lib/enhancers/github/githubIssueAddComment.ts +++ b/browser-extension/src/lib/enhancers/github/githubIssueAddComment.tsx @@ -1,10 +1,11 @@ import OverType, { type OverTypeInstance } from 'overtype' -import type { CommentEnhancer, CommentSpot } from '../../enhancer' -import { logger } from '../../logger' +import type React from 'react' +import type { CommentEnhancer, CommentSpot } from '@/lib/enhancer' +import { logger } from '@/lib/logger' import { modifyDOM } from '../modifyDOM' import { githubHighlighter } from './githubHighlighter' -interface GitHubIssueAddCommentSpot extends CommentSpot { +export interface GitHubIssueAddCommentSpot extends CommentSpot { type: 'GH_ISSUE_ADD_COMMENT' domain: string slug: string // owner/repo @@ -54,16 +55,13 @@ export class GitHubIssueAddCommentEnhancer implements CommentEnhancer + {slug} + Issue #{number} + + ) } } diff --git a/browser-extension/src/lib/enhancers/github/githubPRAddComment.ts b/browser-extension/src/lib/enhancers/github/githubPRAddComment.tsx similarity index 80% rename from browser-extension/src/lib/enhancers/github/githubPRAddComment.ts rename to browser-extension/src/lib/enhancers/github/githubPRAddComment.tsx index 8514000..0b70cb4 100644 --- a/browser-extension/src/lib/enhancers/github/githubPRAddComment.ts +++ b/browser-extension/src/lib/enhancers/github/githubPRAddComment.tsx @@ -1,10 +1,11 @@ import OverType, { type OverTypeInstance } from 'overtype' -import type { CommentEnhancer, CommentSpot } from '../../enhancer' -import { logger } from '../../logger' +import type React from 'react' +import type { CommentEnhancer, CommentSpot } from '@/lib/enhancer' +import { logger } from '@/lib/logger' import { modifyDOM } from '../modifyDOM' import { githubHighlighter } from './githubHighlighter' -interface GitHubPRAddCommentSpot extends CommentSpot { +export interface GitHubPRAddCommentSpot extends CommentSpot { type: 'GH_PR_ADD_COMMENT' // Override to narrow from string to specific union domain: string slug: string // owner/repo @@ -58,16 +59,13 @@ export class GitHubPRAddCommentEnhancer implements CommentEnhancer + {slug} + PR #{number} + + ) } } diff --git a/browser-extension/src/lib/utils.ts b/browser-extension/src/lib/utils.ts new file mode 100644 index 0000000..d32b0fe --- /dev/null +++ b/browser-extension/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/browser-extension/src/styles/globals.css b/browser-extension/src/styles/globals.css new file mode 100644 index 0000000..f1d8c73 --- /dev/null +++ b/browser-extension/src/styles/globals.css @@ -0,0 +1 @@ +@import "tailwindcss"; diff --git a/browser-extension/src/styles/popup-frame.css b/browser-extension/src/styles/popup-frame.css new file mode 100644 index 0000000..49cd2b8 --- /dev/null +++ b/browser-extension/src/styles/popup-frame.css @@ -0,0 +1,13 @@ +/* Popup window frame styles */ +:root { + --popup-width: 600px; +} + +body { + width: var(--popup-width); + padding: 15px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-size: 14px; + line-height: 1.4; + margin: 0; +} diff --git a/browser-extension/tests/lib/enhancers/github.test.ts b/browser-extension/tests/lib/enhancers/github.test.ts index 3952e81..c119651 100644 --- a/browser-extension/tests/lib/enhancers/github.test.ts +++ b/browser-extension/tests/lib/enhancers/github.test.ts @@ -11,7 +11,8 @@ describe('github', () => { const textareas = document.querySelectorAll('textarea') expect(textareas.length).toBe(2) expect(enhancers.tryToEnhance(textareas[0]!)).toBeNull() - expect(enhancers.tryToEnhance(textareas[1]!)?.spot).toMatchInlineSnapshot(` + const enhancedTextarea = enhancers.tryToEnhance(textareas[1]!) + expect(enhancedTextarea?.spot).toMatchInlineSnapshot(` { "domain": "github.com", "number": 517, @@ -20,12 +21,28 @@ describe('github', () => { "unique_key": "github.com:diffplug/selfie:517", } `) + expect(enhancedTextarea?.enhancer.tableRow(enhancedTextarea.spot)).toMatchInlineSnapshot(` + + + diffplug/selfie + + + PR # + 517 + + + `) }) usingHar('gh_issue').it('should create the correct spot object', async () => { const enhancers = new EnhancerRegistry() const textareas = document.querySelectorAll('textarea') expect(textareas.length).toBe(1) - expect(enhancers.tryToEnhance(textareas[0]!)?.spot).toMatchInlineSnapshot(` + const enhancedTextarea = enhancers.tryToEnhance(textareas[0]!) + expect(enhancedTextarea?.spot).toMatchInlineSnapshot(` { "domain": "github.com", "number": 523, @@ -34,5 +51,21 @@ describe('github', () => { "unique_key": "github.com:diffplug/selfie:523", } `) + // Test the tableRow method + expect(enhancedTextarea?.enhancer.tableRow(enhancedTextarea.spot)).toMatchInlineSnapshot(` + + + diffplug/selfie + + + Issue # + 523 + + + `) }) }) diff --git a/browser-extension/tests/playground/PopupPlayground.tsx b/browser-extension/tests/playground/PopupPlayground.tsx new file mode 100644 index 0000000..8a188f7 --- /dev/null +++ b/browser-extension/tests/playground/PopupPlayground.tsx @@ -0,0 +1,31 @@ +import { SpotTable } from '@/components/SpotTable' +import type { CommentState } from '@/entrypoints/background' +import { EnhancerRegistry } from '@/lib/registries' +import { sampleSpots } from './playgroundData' + +export function PopupPlayground() { + const handleSpotClick = (spot: CommentState) => { + alert(`Clicked: ${spot.spot.type}\nTab: ${spot.tab.tabId}`) + } + + const enhancers = new EnhancerRegistry() + + return ( +
+

Open Comment Spots

+ +
+ +
+
+ ) +} diff --git a/browser-extension/tests/playground/index.html b/browser-extension/tests/playground/index.html new file mode 100644 index 0000000..0138cf2 --- /dev/null +++ b/browser-extension/tests/playground/index.html @@ -0,0 +1,13 @@ + + + + + + + Table Playground + + +
+ + + \ No newline at end of file diff --git a/browser-extension/tests/playground/playground.tsx b/browser-extension/tests/playground/playground.tsx new file mode 100644 index 0000000..2ee3653 --- /dev/null +++ b/browser-extension/tests/playground/playground.tsx @@ -0,0 +1,27 @@ +import { createRoot } from 'react-dom/client' +import '@/entrypoints/popup/style.css' +import './style.css' +import { PopupPlayground } from './PopupPlayground' + +const root = createRoot(document.getElementById('root')!) +root.render( +
+
+
+

Popup Simulator

+
+ +
+ +
+ +
+

Development Notes

+
    +
  • The popup frame above matches the exact browser extension popup.
  • +
  • Hot reload is active for instant updates
  • +
+
+
+
, +) diff --git a/browser-extension/tests/playground/playgroundData.tsx b/browser-extension/tests/playground/playgroundData.tsx new file mode 100644 index 0000000..57fd727 --- /dev/null +++ b/browser-extension/tests/playground/playgroundData.tsx @@ -0,0 +1,40 @@ +import type { CommentState } from '@/entrypoints/background' +import type { CommentSpot } from '@/lib/enhancer' +import type { GitHubIssueAddCommentSpot } from '@/lib/enhancers/github/githubIssueAddComment' +import type { GitHubPRAddCommentSpot } from '@/lib/enhancers/github/githubPRAddComment' + +const gh_pr: GitHubPRAddCommentSpot = { + domain: 'github.com', + number: 517, + slug: 'diffplug/selfie', + type: 'GH_PR_ADD_COMMENT', + unique_key: 'github.com:diffplug/selfie:517', +} +const gh_issue: GitHubIssueAddCommentSpot = { + domain: 'github.com', + number: 523, + slug: 'diffplug/selfie', + type: 'GH_ISSUE_ADD_COMMENT', + unique_key: 'github.com:diffplug/selfie:523', +} + +const spots: CommentSpot[] = [gh_pr, gh_issue] + +export const sampleSpots: CommentState[] = spots.map((spot) => { + const state: CommentState = { + drafts: [ + [ + 0, + { + body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', + }, + ], + ], + spot, + tab: { + tabId: 123, + windowId: 456, + }, + } + return state +}) diff --git a/browser-extension/tests/playground/style.css b/browser-extension/tests/playground/style.css new file mode 100644 index 0000000..c56cada --- /dev/null +++ b/browser-extension/tests/playground/style.css @@ -0,0 +1,29 @@ +/* Playground-specific styles - popup styles are imported via popup/style.css */ + +/* Override body styles for playground layout */ +body { + margin: 0; + padding: 2rem; + background: #f8fafc; + min-height: 100vh; + width: auto; /* Override popup's fixed width for playground */ +} + +#root { + max-width: 1200px; + margin: 0; +} + +/* Popup simulator frame */ +.popup-frame { + width: var(--popup-width); + padding: 15px; + font-size: 14px; + line-height: 1.4; + background: white; + border: 1px solid #e2e8f0; + border-radius: 8px; + box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1); + margin: 0; + text-align: left; +} diff --git a/browser-extension/tsconfig.json b/browser-extension/tsconfig.json index 90169d5..d0ce398 100644 --- a/browser-extension/tsconfig.json +++ b/browser-extension/tsconfig.json @@ -8,9 +8,13 @@ "esModuleInterop": true, "exactOptionalPropertyTypes": true, "forceConsistentCasingInFileNames": true, - "jsx": "preserve", + "jsx": "react-jsx", "lib": ["ES2022", "DOM", "DOM.Iterable"], "moduleResolution": "bundler", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, // Emit "noEmit": true, diff --git a/browser-extension/vite.playground.config.ts b/browser-extension/vite.playground.config.ts new file mode 100644 index 0000000..37888e5 --- /dev/null +++ b/browser-extension/vite.playground.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' + +export default defineConfig({ + plugins: [ + react(), + tailwindcss() as any + ], + resolve: { + alias: { + '@': path.resolve('./src') + } + }, + root: 'tests/playground', + server: { + port: 3002, + open: true, + host: true + }, + build: { + outDir: '../../dist-playground', + emptyOutDir: true + } +}) \ No newline at end of file diff --git a/browser-extension/wxt.config.ts b/browser-extension/wxt.config.ts index 7a4699a..6c543af 100644 --- a/browser-extension/wxt.config.ts +++ b/browser-extension/wxt.config.ts @@ -1,6 +1,17 @@ import { defineConfig } from 'wxt' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' +import path from 'path' export default defineConfig({ + vite: () => ({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve('./src') + } + } + }), manifest: { description: 'Syntax highlighting and autosave for comments on GitHub (and other other markdown-friendly websites).', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c8aa1ce..55dc316 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,15 +10,45 @@ importers: browser-extension: dependencies: + '@primer/octicons-react': + specifier: ^19.18.0 + version: 19.18.0(react@19.1.1) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.12)(react@19.1.1) + '@types/react': + specifier: ^19.1.12 + version: 19.1.12 + '@types/react-dom': + specifier: ^19.1.9 + version: 19.1.9(@types/react@19.1.12) '@wxt-dev/webextension-polyfill': specifier: ^1.0.0 - version: 1.0.0(webextension-polyfill@0.12.0)(wxt@0.20.11(@types/node@22.18.1)(jiti@2.5.1)(rollup@4.50.1)(tsx@4.20.5)) + version: 1.0.0(webextension-polyfill@0.12.0)(wxt@0.20.11(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.50.1)(tsx@4.20.5)) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 highlight.js: specifier: ^11.11.1 version: 11.11.1 + lucide-react: + specifier: ^0.543.0 + version: 0.543.0(react@19.1.1) overtype: specifier: workspace:* version: link:../packages/overtype + react: + specifier: ^19.1.1 + version: 19.1.1 + react-dom: + specifier: ^19.1.1 + version: 19.1.1(react@19.1.1) + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 webextension-polyfill: specifier: ^0.12.0 version: 0.12.0 @@ -29,6 +59,9 @@ importers: '@playwright/test': specifier: ^1.46.0 version: 1.55.0 + '@tailwindcss/vite': + specifier: ^4.1.13 + version: 4.1.13(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5)) '@testing-library/jest-dom': specifier: ^6.6.4 version: 6.8.0 @@ -41,6 +74,9 @@ importers: '@types/node': specifier: ^22.16.5 version: 22.18.1 + '@vitejs/plugin-react': + specifier: ^5.0.2 + version: 5.0.2(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5)) '@vitest/coverage-v8': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4) @@ -53,18 +89,27 @@ importers: linkedom: specifier: ^0.18.12 version: 0.18.12 + postcss: + specifier: ^8.5.6 + version: 8.5.6 + tailwindcss: + specifier: ^4.1.13 + version: 4.1.13 tsx: specifier: ^4.19.1 version: 4.20.5 typescript: specifier: ^5.8.3 version: 5.9.2 + vite: + specifier: ^7.1.5 + version: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(tsx@4.20.5) + version: 3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.5) wxt: specifier: ^0.20.7 - version: 0.20.11(@types/node@22.18.1)(jiti@2.5.1)(rollup@4.50.1)(tsx@4.20.5) + version: 0.20.11(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.50.1)(tsx@4.20.5) packages/overtype: dependencies: @@ -117,6 +162,40 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.4': + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -125,15 +204,43 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.28.4': resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.28.2': resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} @@ -542,6 +649,10 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + '@istanbuljs/schema@0.1.3': resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} @@ -598,6 +709,33 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@primer/octicons-react@19.18.0': + resolution: {integrity: sha512-nLFlLmWfz3McbTiOUKVO+iwB15ALYQC9rHeP8K3qM1pyJ8svGaPjGR72BQSEM8ThyQUUodq/Re1n94tO5NNhzQ==} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.3' + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.34': + resolution: {integrity: sha512-LyAREkZHP5pMom7c24meKmJCdhf2hEyvam2q0unr3or9ydwDL+DJ8chTF6Av/RFPb3rH8UFBdMzO5MxTZW97oA==} + '@rollup/rollup-android-arm-eabi@4.50.1': resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} cpu: [arm] @@ -703,10 +841,112 @@ packages: cpu: [x64] os: [win32] + '@tailwindcss/node@4.1.13': + resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} + + '@tailwindcss/oxide-android-arm64@4.1.13': + resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.13': + resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.13': + resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.13': + resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.13': + resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.13': + resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.13': + resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.13': + resolution: {integrity: sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@testing-library/jest-dom@6.8.0': resolution: {integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} @@ -761,6 +1001,14 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@19.1.9': + resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.12': + resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} + '@types/send@0.17.5': resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} @@ -770,6 +1018,12 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@vitejs/plugin-react@5.0.2': + resolution: {integrity: sha512-tmyFgixPZCx2+e6VO9TNITWcCQl8+Nl/E8YbAyPVv85QCc7/A3JrdfG2A8gIzvVhWuzMOVrFW1aReaNxrI6tbw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/coverage-v8@3.2.4': resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} peerDependencies: @@ -953,6 +1207,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.25.4: + resolution: {integrity: sha512-4jYpcjabC606xJ3kw2QwGEZKX0Aw7sgQdZCvIK9dhVSPh76BKo+C+btT1RRofH7B+8iNpEbgGNVWiLki5q93yg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -1002,6 +1261,9 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} + caniuse-lite@1.0.30001741: + resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -1022,6 +1284,10 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + chrome-launcher@1.2.0: resolution: {integrity: sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==} engines: {node: '>=12.13.0'} @@ -1034,6 +1300,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -1070,6 +1339,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1120,6 +1393,9 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -1155,6 +1431,9 @@ packages: resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} engines: {node: '>=18'} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -1248,6 +1527,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} @@ -1293,6 +1576,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.215: + resolution: {integrity: sha512-TIvGp57UpeNetj/wV/xpFNpWGb0b/ROw372lHPx5Aafx02gjTBtWnEEcaSX3W2dLM3OSdGGyHX/cHl01JQsLaQ==} + emoji-regex@10.5.0: resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} @@ -1313,6 +1599,10 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1500,6 +1790,10 @@ packages: resolution: {integrity: sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg==} hasBin: true + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1846,6 +2140,11 @@ packages: canvas: optional: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} @@ -1886,6 +2185,70 @@ packages: lighthouse-logger@2.0.2: resolution: {integrity: sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==} + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -1944,10 +2307,18 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lucide-react@0.543.0: + resolution: {integrity: sha512-fpVfuOQO0V3HBaOA1stIiP/A2fPCXHIleRZL16Mx3HmjTYwNSbimhnFBygs2CAfU1geexMX5ItUcWBGUaqw5CA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + magic-string@0.30.19: resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} @@ -2052,6 +2423,15 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} @@ -2095,6 +2475,9 @@ packages: node-notifier@10.0.1: resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} + node-releases@2.0.20: + resolution: {integrity: sha512-7gK6zSXEH6neM212JgfYFXe+GmZQM+fia5SsusuBIUgnPheLFBmIPhtFoAQRj8/7wASYQnbDlHPVwY0BefoFgA==} + normalize-package-data@3.0.3: resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} engines: {node: '>=10'} @@ -2366,6 +2749,19 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-dom@19.1.1: + resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} + peerDependencies: + react: ^19.1.1 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.1.1: + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + engines: {node: '>=0.10.0'} + read-pkg-up@8.0.0: resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} engines: {node: '>=12'} @@ -2465,12 +2861,19 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -2666,6 +3069,20 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwindcss@4.1.13: + resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} + + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + test-exclude@7.0.1: resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} engines: {node: '>=18'} @@ -2808,6 +3225,12 @@ packages: resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} engines: {node: '>=18.12.0'} + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + update-notifier@7.3.1: resolution: {integrity: sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==} engines: {node: '>=18'} @@ -3032,9 +3455,16 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -3101,16 +3531,109 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/compat-data@7.28.4': {} + + '@babel/core@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.4 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + '@babel/parser@7.28.4': dependencies: '@babel/types': 7.28.4 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': + dependencies: + '@babel/core': 7.28.4 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/runtime@7.28.2': {} + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@babel/traverse@7.28.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.4 + '@babel/template': 7.27.2 + '@babel/types': 7.28.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -3351,6 +3874,10 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + '@istanbuljs/schema@0.1.3': {} '@jridgewell/gen-mapping@0.3.13': @@ -3405,6 +3932,25 @@ snapshots: '@polka/url@1.0.0-next.29': {} + '@primer/octicons-react@19.18.0(react@19.1.1)': + dependencies: + react: 19.1.1 + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@rolldown/pluginutils@1.0.0-beta.34': {} + '@rollup/rollup-android-arm-eabi@4.50.1': optional: true @@ -3468,6 +4014,77 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.50.1': optional: true + '@tailwindcss/node@4.1.13': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.5.1 + lightningcss: 1.30.1 + magic-string: 0.30.19 + source-map-js: 1.2.1 + tailwindcss: 4.1.13 + + '@tailwindcss/oxide-android-arm64@4.1.13': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.13': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.13': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.13': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.13': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + optional: true + + '@tailwindcss/oxide@4.1.13': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-x64': 4.1.13 + '@tailwindcss/oxide-freebsd-x64': 4.1.13 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-x64-musl': 4.1.13 + '@tailwindcss/oxide-wasm32-wasi': 4.1.13 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 + + '@tailwindcss/vite@4.1.13(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5))': + dependencies: + '@tailwindcss/node': 4.1.13 + '@tailwindcss/oxide': 4.1.13 + tailwindcss: 4.1.13 + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) + '@testing-library/jest-dom@6.8.0': dependencies: '@adobe/css-tools': 4.4.4 @@ -3477,6 +4094,27 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.4 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.4 + '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 @@ -3534,6 +4172,14 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/react-dom@19.1.9(@types/react@19.1.12)': + dependencies: + '@types/react': 19.1.12 + + '@types/react@19.1.12': + dependencies: + csstype: 3.1.3 + '@types/send@0.17.5': dependencies: '@types/mime': 1.3.5 @@ -3550,6 +4196,18 @@ snapshots: '@types/node': 22.18.1 optional: true + '@vitejs/plugin-react@5.0.2(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5))': + dependencies: + '@babel/core': 7.28.4 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@rolldown/pluginutils': 1.0.0-beta.34 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) + transitivePeerDependencies: + - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': dependencies: '@ampproject/remapping': 2.3.0 @@ -3565,7 +4223,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(tsx@4.20.5) + vitest: 3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.5) transitivePeerDependencies: - supports-color @@ -3577,13 +4235,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5))': + '@vitest/mocker@3.2.4(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5) + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) '@vitest/pretty-format@3.2.4': dependencies: @@ -3614,7 +4272,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(tsx@4.20.5) + vitest: 3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.5) '@vitest/utils@3.2.4': dependencies: @@ -3643,10 +4301,10 @@ snapshots: async-mutex: 0.5.0 dequal: 2.0.3 - '@wxt-dev/webextension-polyfill@1.0.0(webextension-polyfill@0.12.0)(wxt@0.20.11(@types/node@22.18.1)(jiti@2.5.1)(rollup@4.50.1)(tsx@4.20.5))': + '@wxt-dev/webextension-polyfill@1.0.0(webextension-polyfill@0.12.0)(wxt@0.20.11(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.50.1)(tsx@4.20.5))': dependencies: webextension-polyfill: 0.12.0 - wxt: 0.20.11(@types/node@22.18.1)(jiti@2.5.1)(rollup@4.50.1)(tsx@4.20.5) + wxt: 0.20.11(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.50.1)(tsx@4.20.5) accepts@1.3.8: dependencies: @@ -3769,6 +4427,13 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.25.4: + dependencies: + caniuse-lite: 1.0.30001741 + electron-to-chromium: 1.5.215 + node-releases: 2.0.20 + update-browserslist-db: 1.1.3(browserslist@4.25.4) + buffer-crc32@0.2.13: {} buffer-from@1.1.2: {} @@ -3824,6 +4489,8 @@ snapshots: camelcase@8.0.0: {} + caniuse-lite@1.0.30001741: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -3845,6 +4512,8 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@3.0.0: {} + chrome-launcher@1.2.0: dependencies: '@types/node': 22.18.1 @@ -3860,6 +4529,10 @@ snapshots: dependencies: consola: 3.4.2 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + cli-boxes@3.0.0: {} cli-cursor@4.0.0: @@ -3900,6 +4573,8 @@ snapshots: clone@1.0.4: {} + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3947,6 +4622,8 @@ snapshots: content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.0.6: {} cookie@0.7.1: {} @@ -3980,6 +4657,8 @@ snapshots: '@asamuzakjp/css-color': 3.2.0 rrweb-cssom: 0.8.0 + csstype@3.1.3: {} + data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -4039,6 +4718,8 @@ snapshots: destroy@1.2.0: {} + detect-libc@2.0.4: {} + dom-accessibility-api@0.6.3: {} dom-serializer@2.0.0: @@ -4083,6 +4764,8 @@ snapshots: ee-first@1.1.1: {} + electron-to-chromium@1.5.215: {} + emoji-regex@10.5.0: {} emoji-regex@8.0.0: {} @@ -4097,6 +4780,11 @@ snapshots: dependencies: once: 1.4.0 + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.3 + entities@4.5.0: {} entities@6.0.1: {} @@ -4337,6 +5025,8 @@ snapshots: which: 1.2.4 winreg: 0.0.12 + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-east-asian-width@1.3.1: {} @@ -4678,6 +5368,8 @@ snapshots: - supports-color - utf-8-validate + jsesc@3.1.0: {} + json-parse-even-better-errors@2.3.1: {} json-parse-even-better-errors@3.0.2: {} @@ -4718,6 +5410,51 @@ snapshots: transitivePeerDependencies: - supports-color + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + lines-and-columns@1.2.4: {} lines-and-columns@2.0.4: {} @@ -4779,10 +5516,18 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 + lucide-react@0.543.0(react@19.1.1): + dependencies: + react: 19.1.1 + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4875,6 +5620,12 @@ snapshots: minipass@7.1.2: {} + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + mlly@1.8.0: dependencies: acorn: 8.15.0 @@ -4920,6 +5671,8 @@ snapshots: uuid: 8.3.2 which: 2.0.2 + node-releases@2.0.20: {} + normalize-package-data@3.0.3: dependencies: hosted-git-info: 4.1.0 @@ -5232,6 +5985,15 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-dom@19.1.1(react@19.1.1): + dependencies: + react: 19.1.1 + scheduler: 0.26.0 + + react-refresh@0.17.0: {} + + react@19.1.1: {} + read-pkg-up@8.0.0: dependencies: find-up: 5.0.0 @@ -5352,10 +6114,14 @@ snapshots: dependencies: xmlchars: 2.2.0 + scheduler@0.26.0: {} + scule@1.3.0: {} secure-compare@3.0.1: {} + semver@6.3.1: {} + semver@7.7.2: {} send@0.19.0: @@ -5568,6 +6334,21 @@ snapshots: symbol-tree@3.2.4: {} + tailwind-merge@3.3.1: {} + + tailwindcss@4.1.13: {} + + tapable@2.2.3: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + test-exclude@7.0.1: dependencies: '@istanbuljs/schema': 0.1.3 @@ -5698,6 +6479,12 @@ snapshots: picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 + update-browserslist-db@1.1.3(browserslist@4.25.4): + dependencies: + browserslist: 4.25.4 + escalade: 3.2.0 + picocolors: 1.1.1 + update-notifier@7.3.1: dependencies: boxen: 8.0.1 @@ -5726,13 +6513,13 @@ snapshots: vary@1.1.2: {} - vite-node@3.2.4(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5): + vite-node@3.2.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5) + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) transitivePeerDependencies: - '@types/node' - jiti @@ -5747,7 +6534,7 @@ snapshots: - tsx - yaml - vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5): + vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) @@ -5759,13 +6546,14 @@ snapshots: '@types/node': 22.18.1 fsevents: 2.3.3 jiti: 2.5.1 + lightningcss: 1.30.1 tsx: 4.20.5 - vitest@3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(tsx@4.20.5): + vitest@3.2.4(@types/node@22.18.1)(@vitest/ui@3.2.4)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.5): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5)) + '@vitest/mocker': 3.2.4(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -5783,8 +6571,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5) - vite-node: 3.2.4(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5) + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) + vite-node: 3.2.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.18.1 @@ -5913,7 +6701,7 @@ snapshots: dependencies: is-wsl: 3.1.0 - wxt@0.20.11(@types/node@22.18.1)(jiti@2.5.1)(rollup@4.50.1)(tsx@4.20.5): + wxt@0.20.11(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.50.1)(tsx@4.20.5): dependencies: '@1natsu/wait-element': 4.1.2 '@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.50.1) @@ -5957,8 +6745,8 @@ snapshots: publish-browser-extension: 3.0.2 scule: 1.3.0 unimport: 5.2.0 - vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5) - vite-node: 3.2.4(@types/node@22.18.1)(jiti@2.5.1)(tsx@4.20.5) + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) + vite-node: 3.2.4(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.5) web-ext-run: 0.2.4 transitivePeerDependencies: - '@types/node' @@ -5991,8 +6779,12 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yallist@4.0.0: {} + yallist@5.0.0: {} + yargs-parser@20.2.9: {} yargs-parser@21.1.1: {}