From 138b21ee54fb6aa41b74d32ca49161725183b4e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 00:32:55 +0000 Subject: [PATCH 1/8] =?UTF-8?q?Phase=20A=20=E2=80=94=20corrections:=20cont?= =?UTF-8?q?act=20email=20+=20personal=20domain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the contact email (PR #67 changed it the wrong way) and moves the personal-site link to the new britx.me domain. - Email pedrohbrito@me.com -> pedrobritx@gmail.com in the canonical AUTHOR_EMAIL (features/about/links.ts), README license contact, and LICENSE.md. - "Website" brand link -> https://britx.me (was pedrobritx.github.io/EwP); updated tests/unit/about.test.tsx which guards the href. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013kzE8hPSJxu19tzs7kuMMa --- LICENSE.md | 2 +- README.md | 2 +- src/features/about/links.ts | 6 +++--- tests/unit/about.test.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 18aa994..526a6c9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -49,7 +49,7 @@ If you are an Organization (any entity larger than one person), please contact the Author to arrange terms. Pricing is set individually and scaled to the size and nature of your use: -- **Email:** pedrohbrito@me.com +- **Email:** pedrobritx@gmail.com - **GitHub:** - **LinkedIn:** diff --git a/README.md b/README.md index 40d2cc6..6777d3c 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ license — see [`LICENSE.md`](LICENSE.md): **credit the developer** and keep the link back to this project. - **Organizations — get in touch.** Any team or company larger than one person needs a commercial license. Pricing is set individually and fairly — - contact **pedrohbrito@me.com**. + contact **pedrobritx@gmail.com**. --- diff --git a/src/features/about/links.ts b/src/features/about/links.ts index 5a48244..5f587af 100644 --- a/src/features/about/links.ts +++ b/src/features/about/links.ts @@ -11,7 +11,7 @@ export interface BrandLink { /** Canonical project + author links, shared by the About page and footer. */ export const REPO_URL = 'https://github.com/pedrobritx/verbalis' export const AUTHOR_NAME = 'Pedro Brito' -export const AUTHOR_EMAIL = 'pedrohbrito@me.com' +export const AUTHOR_EMAIL = 'pedrobritx@gmail.com' export const LICENSE_URL = `${REPO_URL}/blob/main/LICENSE.md` export const BRAND_LINKS: BrandLink[] = [ @@ -29,8 +29,8 @@ export const BRAND_LINKS: BrandLink[] = [ }, { label: 'Website', - description: 'pedrobritx — portfolio', - href: 'https://pedrobritx.github.io/EwP/', + description: 'Pedro Brito — britx.me', + href: 'https://britx.me', icon: Globe, }, { diff --git a/tests/unit/about.test.tsx b/tests/unit/about.test.tsx index f6d92d6..dd5f34d 100644 --- a/tests/unit/about.test.tsx +++ b/tests/unit/about.test.tsx @@ -20,7 +20,7 @@ describe('AboutPage', () => { const hrefs = Array.from(document.querySelectorAll('a')).map((a) => a.getAttribute('href')) expect(hrefs).toContain('https://github.com/pedrobritx/verbalis') expect(hrefs).toContain('https://www.linkedin.com/in/pedrobritx/') - expect(hrefs).toContain('https://pedrobritx.github.io/EwP/') + expect(hrefs).toContain('https://britx.me') expect(hrefs).toContain('https://buymeacoffee.com/pedrobritx') }) }) From 258fd2369c241eec5015d6a4db58977b711b5807 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 00:38:54 +0000 Subject: [PATCH 2/8] =?UTF-8?q?Phase=20B=20=E2=80=94=20quick=20lookup=20fo?= =?UTF-8?q?llows=20the=20open=20project's=20languages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The quick lookup seeded its languages from the global lookup defaults, ignoring the project you're working in; the import dialog hardcoded en→es. Both now use the context that's already available. - useQuickLookupStore carries the active project's languages (published by EditorPage while mounted, cleared on unmount) and seeds openWith() from them; an explicit openWith(langs) argument still wins. TranslateWorkspace gains initialSourceLang/initialTargetLang props that take precedence over the global defaults, with auto-detection/manual selection unchanged. - ImportDialog seeds its language pair from the saved lookup defaults / last-used languages (distinct fallback pt→en for this PT↔EN-oriented tool instead of a fixed en→es) and persists the chosen pair on import so the next import and the lookup remember it. - New tests/unit/lookup.store.test.ts covers the seeding precedence. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013kzE8hPSJxu19tzs7kuMMa --- src/features/editor/EditorPage.tsx | 11 ++++ src/features/import/ImportDialog.tsx | 32 +++++++++++- src/features/lookup/QuickLookupDialog.tsx | 9 +++- src/features/lookup/useQuickLookupStore.ts | 31 +++++++++-- src/features/translate/TranslateWorkspace.tsx | 14 +++-- tests/e2e/import-and-edit.spec.ts | 3 +- tests/unit/lookup.store.test.ts | 52 +++++++++++++++++++ 7 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 tests/unit/lookup.store.test.ts diff --git a/src/features/editor/EditorPage.tsx b/src/features/editor/EditorPage.tsx index 36a9661..4232cf0 100644 --- a/src/features/editor/EditorPage.tsx +++ b/src/features/editor/EditorPage.tsx @@ -34,6 +34,7 @@ import { AddTermDialog } from './glossary/AddTermDialog' import { GlossaryEditController } from './glossary/GlossaryEditController' import { ConcordanceDialog } from './concordance/ConcordanceDialog' import { useProjectDialogsStore } from '@/features/projects/useProjectDialogsStore' +import { useQuickLookupStore } from '@/features/lookup/useQuickLookupStore' import { translateWith, resolveDefaultProvider, MTError } from '@/core/mt' import { getMTSettings, @@ -108,6 +109,7 @@ export default function EditorPage() { const openFindReplace = useFindReplaceStore((s) => s.setOpen) const openAnalysis = useAnalysisStore((s) => s.setOpen) const openProjectDialog = useProjectDialogsStore((s) => s.open) + const setLookupProjectLangs = useQuickLookupStore((s) => s.setProjectLangs) const [toolMsg, setToolMsg] = useState(null) const registerHandle = useCallback((index: number, handle: SegmentEditorHandle | null) => { @@ -127,6 +129,15 @@ export default function EditorPage() { return () => resetWorkflow() }, [project?.cloud?.role, project?.cloud?.stage, setWorkflow, resetWorkflow]) + // Publish the project's languages so every quick-lookup trigger (top bar, + // command palette, Ctrl-selection) defaults to them instead of the global + // lookup defaults. Cleared on unmount so lookups outside a project fall back. + useEffect(() => { + if (!project) return + setLookupProjectLangs({ source: project.sourceLang, target: project.targetLang }) + return () => setLookupProjectLangs(null) + }, [project?.sourceLang, project?.targetLang, setLookupProjectLangs]) + // A translator in the review stage may only suggest — pin the edit mode. useEffect(() => { if (forceSuggest) setEditMode('suggesting') diff --git a/src/features/import/ImportDialog.tsx b/src/features/import/ImportDialog.tsx index 6343c2d..fdde9e7 100644 --- a/src/features/import/ImportDialog.tsx +++ b/src/features/import/ImportDialog.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { Dialog, DialogContent, @@ -18,6 +18,11 @@ import type { ConnectorFile, StorageConnector } from '@/extensions/connectors/ty import { isGdriveAvailable } from '@/extensions/connectors/gdrive/config' import { isOnedriveAvailable } from '@/extensions/connectors/onedrive/config' import { useImportProject } from './useImportProject' +import { + getLookupSettings, + settingsRepo, + LOOKUP_SETTINGS_KEY, +} from '@/storage/repositories/settingsRepo' interface ImportDialogProps { open: boolean @@ -54,6 +59,25 @@ export function ImportDialog({ open, onOpenChange }: ImportDialogProps) { const gdrive = isGdriveAvailable() const onedrive = isOnedriveAvailable() + // Seed the language pair from the user's saved lookup defaults / last-used + // languages when the dialog opens, instead of a fixed guess. Falls back to a + // sensible distinct pair (this is a PT↔EN-oriented tool) so the monolingual + // "source ≠ target" guard is satisfied out of the box. + useEffect(() => { + if (!open) return + void getLookupSettings().then((l) => { + const target = l.defaultTargetLang + const source = + l.lastSourceLang && l.lastSourceLang !== target + ? l.lastSourceLang + : target === 'en' + ? 'pt' + : 'en' + setSourceLang(source) + setTargetLang(target) + }) + }, [open]) + function reset() { setName('') setSourceLang('en') @@ -96,6 +120,12 @@ export function ImportDialog({ open, onOpenChange }: ImportDialogProps) { if (!bilingual && sourceLang === targetLang) return try { await importProject({ file, name: name.trim(), sourceLang, targetLang }) + // Remember the working languages so the next import and the quick lookup + // default to them. + void settingsRepo.set(LOOKUP_SETTINGS_KEY, { + defaultTargetLang: targetLang, + lastSourceLang: sourceLang, + }) reset() onOpenChange(false) } catch { diff --git a/src/features/lookup/QuickLookupDialog.tsx b/src/features/lookup/QuickLookupDialog.tsx index 99d9c30..10a948a 100644 --- a/src/features/lookup/QuickLookupDialog.tsx +++ b/src/features/lookup/QuickLookupDialog.tsx @@ -6,6 +6,8 @@ import { useQuickLookupStore } from './useQuickLookupStore' export function QuickLookupDialog() { const open = useQuickLookupStore((s) => s.open) const prefill = useQuickLookupStore((s) => s.prefill) + const sourceLang = useQuickLookupStore((s) => s.sourceLang) + const targetLang = useQuickLookupStore((s) => s.targetLang) const close = useQuickLookupStore((s) => s.close) return ( @@ -52,7 +54,12 @@ export function QuickLookupDialog() {
- +
diff --git a/src/features/lookup/useQuickLookupStore.ts b/src/features/lookup/useQuickLookupStore.ts index f4560d4..ab95b0b 100644 --- a/src/features/lookup/useQuickLookupStore.ts +++ b/src/features/lookup/useQuickLookupStore.ts @@ -1,15 +1,40 @@ import { create } from 'zustand' +export interface LookupLangs { + source: string + target: string +} + interface QuickLookupState { open: boolean prefill: string - openWith: (prefill?: string) => void + /** Effective languages to seed the lookup with (project langs when opened from + * inside a project, else undefined → fall back to the global lookup defaults). */ + sourceLang?: string + targetLang?: string + /** The active project's languages, published by the editor while mounted so + * every lookup trigger defaults to them without prop-drilling. */ + projectLangs: LookupLangs | null + openWith: (prefill?: string, langs?: LookupLangs) => void + setProjectLangs: (langs: LookupLangs | null) => void close: () => void } -export const useQuickLookupStore = create((set) => ({ +export const useQuickLookupStore = create((set, get) => ({ open: false, prefill: '', - openWith: (prefill = '') => set({ open: true, prefill }), + sourceLang: undefined, + targetLang: undefined, + projectLangs: null, + openWith: (prefill = '', langs) => { + const effective = langs ?? get().projectLangs ?? undefined + set({ + open: true, + prefill, + sourceLang: effective?.source, + targetLang: effective?.target, + }) + }, + setProjectLangs: (projectLangs) => set({ projectLangs }), close: () => set({ open: false, prefill: '' }), })) diff --git a/src/features/translate/TranslateWorkspace.tsx b/src/features/translate/TranslateWorkspace.tsx index 22d4989..57f781f 100644 --- a/src/features/translate/TranslateWorkspace.tsx +++ b/src/features/translate/TranslateWorkspace.tsx @@ -34,6 +34,10 @@ interface TranslateWorkspaceProps { prefill?: string autoFocus?: boolean testIdPrefix?: string + /** Seed languages (e.g. the open project's), taking precedence over the global + * lookup defaults. Auto-detection and manual selection still apply after. */ + initialSourceLang?: string + initialTargetLang?: string } export function TranslateWorkspace({ @@ -41,6 +45,8 @@ export function TranslateWorkspace({ prefill = '', autoFocus = true, testIdPrefix = 'quick-lookup', + initialSourceLang, + initialTargetLang, }: TranslateWorkspaceProps) { const online = useNetworkStatus() @@ -74,13 +80,15 @@ export function TranslateWorkspace({ if (cancelled) return setMtSettings(mt) setLookupSettings(lookup) - setTargetLang(lookup.defaultTargetLang) - if (lookup.lastSourceLang) setSourceLang(lookup.lastSourceLang) + // Injected (project) languages win over the global lookup defaults. + setTargetLang(initialTargetLang ?? lookup.defaultTargetLang) + const seedSource = initialSourceLang ?? lookup.lastSourceLang + if (seedSource) setSourceLang(seedSource) }) return () => { cancelled = true } - }, [active]) + }, [active, initialSourceLang, initialTargetLang]) useEffect(() => { if (!active) return diff --git a/tests/e2e/import-and-edit.spec.ts b/tests/e2e/import-and-edit.spec.ts index 0d72b0b..c2972b1 100644 --- a/tests/e2e/import-and-edit.spec.ts +++ b/tests/e2e/import-and-edit.spec.ts @@ -14,7 +14,8 @@ test('import a markdown file, edit a segment, confirm with Ctrl+Enter', async ({ await page.setInputFiles('#import-file', FIXTURE) await expect(page.locator('#import-name')).toHaveValue('sample') - // Defaults are en → es; just submit. + // Default pair (pt → en with no saved lookup history) is source ≠ target, so + // the monolingual guard is satisfied — just submit. await page.getByRole('button', { name: 'Import', exact: true }).click() await expect(page).toHaveURL(/#\/project\//) diff --git a/tests/unit/lookup.store.test.ts b/tests/unit/lookup.store.test.ts new file mode 100644 index 0000000..26e70ac --- /dev/null +++ b/tests/unit/lookup.store.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect, beforeEach } from 'vitest' +import { useQuickLookupStore } from '@/features/lookup/useQuickLookupStore' + +/** + * The quick-lookup store seeds its languages from (in precedence order) an + * explicit `openWith` argument, then the active project's languages published by + * the editor, then nothing (so the workspace falls back to the global lookup + * defaults). This proves that precedence. + */ +describe('useQuickLookupStore language seeding', () => { + beforeEach(() => { + useQuickLookupStore.setState({ + open: false, + prefill: '', + sourceLang: undefined, + targetLang: undefined, + projectLangs: null, + }) + }) + + it('leaves languages unset when no project langs and no explicit langs', () => { + useQuickLookupStore.getState().openWith('hello') + const s = useQuickLookupStore.getState() + expect(s.open).toBe(true) + expect(s.prefill).toBe('hello') + expect(s.sourceLang).toBeUndefined() + expect(s.targetLang).toBeUndefined() + }) + + it('uses the published project languages when present', () => { + useQuickLookupStore.getState().setProjectLangs({ source: 'pt', target: 'en' }) + useQuickLookupStore.getState().openWith('termo') + const s = useQuickLookupStore.getState() + expect(s.sourceLang).toBe('pt') + expect(s.targetLang).toBe('en') + }) + + it('lets an explicit openWith argument win over the project languages', () => { + useQuickLookupStore.getState().setProjectLangs({ source: 'pt', target: 'en' }) + useQuickLookupStore.getState().openWith('mot', { source: 'fr', target: 'de' }) + const s = useQuickLookupStore.getState() + expect(s.sourceLang).toBe('fr') + expect(s.targetLang).toBe('de') + }) + + it('clears project languages when the editor unmounts', () => { + useQuickLookupStore.getState().setProjectLangs({ source: 'pt', target: 'en' }) + useQuickLookupStore.getState().setProjectLangs(null) + useQuickLookupStore.getState().openWith('x') + expect(useQuickLookupStore.getState().sourceLang).toBeUndefined() + }) +}) From ac38224dba53f81aaf687ee46a8ccf9af3191cb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 00:45:53 +0000 Subject: [PATCH 3/8] =?UTF-8?q?Phase=20C=20=E2=80=94=20use=20the=20signed-?= =?UTF-8?q?in=20account=20name=20for=20review=20&=20collaboration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attribution ignored the account name even when signed in: tracked changes and comments showed "You", presence showed "Anonymous", and Settings asked for a separate local name. Now the account name is used automatically. - New features/account/displayName.ts: a pure resolveDisplayName(local, auth, fallback) + reactive useDisplayName() that prefer the account name (profileDisplayName ?? user.displayName) when authenticated, else the device-local name, else a friendly fallback. Two fallbacks kept for their distinct audiences: SELF_NAME_FALLBACK ("You") and PEER_NAME_FALLBACK ("Anonymous"). authorId stays device-local (D8) — only the name is account-aware. - useLocalAuthor (tracked-change/comment attribution) and syncManager (collaboration presence) route their name through the resolver. - versionRepo.saveNamed/signOff/capture accept an optional caller-resolved name so "Approved by {name}" and version author use the account name; the storage layer stays account-agnostic (name injected by VersionHistoryPanel via useLocalAuthor). authorId unchanged. - ProfileSettingsSection stops asking for a local name when signed in (shows the account name + a pointer to Account settings) and drops a stale "once LAN collaboration lands" comment (it shipped). - Tests: displayName resolver matrix (7). flag-off build verified GoTrueClient-free — the wider auth-store imports don't pull Supabase in. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013kzE8hPSJxu19tzs7kuMMa --- src/features/account/displayName.ts | 49 +++++++++++++ .../editor/history/VersionHistoryPanel.tsx | 8 ++- src/features/editor/useLocalAuthor.ts | 5 +- .../settings/ProfileSettingsSection.tsx | 22 +++++- src/storage/repositories/versionRepo.ts | 23 +++++-- src/storage/sync/syncManager.ts | 5 +- tests/unit/displayName.test.ts | 68 +++++++++++++++++++ 7 files changed, 167 insertions(+), 13 deletions(-) create mode 100644 src/features/account/displayName.ts create mode 100644 tests/unit/displayName.test.ts diff --git a/src/features/account/displayName.ts b/src/features/account/displayName.ts new file mode 100644 index 0000000..b63ab65 --- /dev/null +++ b/src/features/account/displayName.ts @@ -0,0 +1,49 @@ +import { useAuthStore, type AuthUser, type AuthStatus } from './useAuthStore' + +/** + * The effective display name for authored actions — tracked changes, comments, + * collaboration presence, version sign-off. When signed in, the account name is + * used automatically, so a signed-in user never has to enter a separate local + * name. Otherwise the device-local name is used, and only when neither exists + * does a friendly fallback show. + * + * Two fallbacks, because the word differs by audience: your own unnamed + * attribution reads as "You"; an unnamed peer shown to *others* reads as + * "Anonymous". + */ +export const SELF_NAME_FALLBACK = 'You' +export const PEER_NAME_FALLBACK = 'Anonymous' + +/** The auth fields the resolver reads — a slice, so it is pure and testable. */ +export interface AuthNameSlice { + status: AuthStatus + user: AuthUser | null + profileDisplayName: string | null +} + +/** + * Resolve the effective name: signed-in account name → device-local name → + * `fallback`. Pure over the auth slice. + */ +export function resolveDisplayName( + local: string | null | undefined, + auth: AuthNameSlice, + fallback: string = SELF_NAME_FALLBACK, +): string { + if (auth.status === 'authenticated') { + const account = (auth.profileDisplayName ?? auth.user?.displayName ?? '').trim() + if (account) return account + } + return (local ?? '').trim() || fallback +} + +/** Reactive `resolveDisplayName` bound to the auth store. */ +export function useDisplayName( + local: string | null | undefined, + fallback: string = SELF_NAME_FALLBACK, +): string { + const status = useAuthStore((s) => s.status) + const user = useAuthStore((s) => s.user) + const profileDisplayName = useAuthStore((s) => s.profileDisplayName) + return resolveDisplayName(local, { status, user, profileDisplayName }, fallback) +} diff --git a/src/features/editor/history/VersionHistoryPanel.tsx b/src/features/editor/history/VersionHistoryPanel.tsx index ad09532..8da47b6 100644 --- a/src/features/editor/history/VersionHistoryPanel.tsx +++ b/src/features/editor/history/VersionHistoryPanel.tsx @@ -4,6 +4,7 @@ import { History, RotateCcw, Save, BadgeCheck } from 'lucide-react' import { Button } from '@/components/ui/button' import { versionRepo } from '@/storage/repositories/versionRepo' import { useCanReview } from '../workflow/useWorkflowStore' +import { useLocalAuthor } from '../useLocalAuthor' import { relativeTime } from './relativeTime' interface VersionHistoryPanelProps { @@ -26,11 +27,14 @@ export function VersionHistoryPanel({ projectId }: VersionHistoryPanelProps) { // Sign-off is a review action — revisor / project_manager only (§5.3). A // local-only project resolves to PM-of-self, so it's always available there. const canReview = useCanReview() + // Account-aware name (account name when signed in, else the device-local one) + // so versions and sign-offs are attributed consistently across the app. + const { authorName } = useLocalAuthor() async function handleSave() { setSaving(true) try { - await versionRepo.saveNamed(projectId, label) + await versionRepo.saveNamed(projectId, label, undefined, authorName) setLabel('') } finally { setSaving(false) @@ -40,7 +44,7 @@ export function VersionHistoryPanel({ projectId }: VersionHistoryPanelProps) { async function handleSignOff() { setSigningOff(true) try { - await versionRepo.signOff(projectId) + await versionRepo.signOff(projectId, undefined, authorName) } finally { setSigningOff(false) } diff --git a/src/features/editor/useLocalAuthor.ts b/src/features/editor/useLocalAuthor.ts index 97b9ddf..873b0d3 100644 --- a/src/features/editor/useLocalAuthor.ts +++ b/src/features/editor/useLocalAuthor.ts @@ -7,6 +7,7 @@ import { type LocalAuthor, type ProfileSettings, } from '@/storage/repositories/settingsRepo' +import { useDisplayName } from '@/features/account/displayName' // Mint/persist the stable author id once for the whole app, shared across every // segment editor so we don't write it N times on load. @@ -39,6 +40,8 @@ export function useLocalAuthor(): LocalAuthor { // Prefer the reactive stored id once available; fall back to the minted one. const id = profile?.authorId ?? authorId - const name = (profile?.displayName ?? '').trim() || 'You' + // When signed in, attribute to the account name automatically; else the + // device-local name, else "You". + const name = useDisplayName(profile?.displayName) return { authorId: id, authorName: name } } diff --git a/src/features/settings/ProfileSettingsSection.tsx b/src/features/settings/ProfileSettingsSection.tsx index f0129d6..262667f 100644 --- a/src/features/settings/ProfileSettingsSection.tsx +++ b/src/features/settings/ProfileSettingsSection.tsx @@ -8,6 +8,7 @@ import { PROFILE_SETTINGS_KEY, type ProfileSettings, } from '@/storage/repositories/settingsRepo' +import { useAuthStore } from '@/features/account/useAuthStore' export function ProfileSettingsSection() { const stored = useLiveQuery( @@ -15,6 +16,8 @@ export function ProfileSettingsSection() { [], ) const [draft, setDraft] = useState(DEFAULT_PROFILE_SETTINGS) + const signedIn = useAuthStore((s) => s.status === 'authenticated') + const accountName = useAuthStore((s) => s.profileDisplayName ?? s.user?.displayName ?? null) useEffect(() => { if (stored !== undefined) setDraft(mergeProfileSettings(stored ?? undefined)) @@ -26,11 +29,26 @@ export function ProfileSettingsSection() { await settingsRepo.set(PROFILE_SETTINGS_KEY, next) } + // Signed in: attribution uses your account name automatically, so there's no + // separate local name to enter — point at Account settings instead of asking. + if (signedIn) { + return ( +

+ Your name for comments, tracked changes and collaboration comes from your + account{accountName ? ` — ${accountName}` : ''}. Change it in Account settings. +

+ ) + } + return (

- The name attached to comments you write. It will also identify you to - peers once local-network collaboration lands. Stored on this device only. + The name attached to your comments, tracked changes and collaboration + presence. Stored on this device only.

diff --git a/src/features/editor/peers/PeersPanel.tsx b/src/features/editor/peers/PeersPanel.tsx index 27337a7..2a523d6 100644 --- a/src/features/editor/peers/PeersPanel.tsx +++ b/src/features/editor/peers/PeersPanel.tsx @@ -2,6 +2,7 @@ import { Users, Wifi } from 'lucide-react' import { shareRepo } from '@/storage/repositories/shareRepo' import { isTauri } from '@/storage/sync/platform' import { usePresenceStore } from './usePresenceStore' +import { PEER_NAME_FALLBACK } from '@/features/account/displayName' /** * The Peers sidebar tab (Foundation F3, Phase 14). @@ -71,7 +72,7 @@ export function PeersPanel({ projectId }: PeersPanelProps) { style={{ background: peer.color }} /> - {peer.displayName || 'Anonymous'} + {peer.displayName || PEER_NAME_FALLBACK} {peer.activeSegmentId && ( diff --git a/src/features/editor/rich/RemoteCaretOverlay.tsx b/src/features/editor/rich/RemoteCaretOverlay.tsx index 368a64a..a131810 100644 --- a/src/features/editor/rich/RemoteCaretOverlay.tsx +++ b/src/features/editor/rich/RemoteCaretOverlay.tsx @@ -4,6 +4,7 @@ import { $getRoot } from 'lexical' import { mapOffsetToNode } from '@/core/spell/offsets' import { usePresenceStore } from '../peers/usePresenceStore' import { buildLeafSpans } from './richOffsets' +import { PEER_NAME_FALLBACK } from '@/features/account/displayName' /** * Live remote cursors for the rich target editor (ROADMAP §4.4.1). @@ -71,7 +72,7 @@ export function RemoteCaretOverlay({ const rect = range.getClientRects()[0] ?? range.getBoundingClientRect() next.push({ peerId: p.peerId, - name: p.displayName || 'Anonymous', + name: p.displayName || PEER_NAME_FALLBACK, color: p.color, left: rect.left - crect.left, top: rect.top - crect.top, diff --git a/src/features/lookup/QuickLookupDialog.tsx b/src/features/lookup/QuickLookupDialog.tsx index 10a948a..e43d79a 100644 --- a/src/features/lookup/QuickLookupDialog.tsx +++ b/src/features/lookup/QuickLookupDialog.tsx @@ -8,10 +8,10 @@ export function QuickLookupDialog() { const prefill = useQuickLookupStore((s) => s.prefill) const sourceLang = useQuickLookupStore((s) => s.sourceLang) const targetLang = useQuickLookupStore((s) => s.targetLang) - const close = useQuickLookupStore((s) => s.close) + const setOpen = useQuickLookupStore((s) => s.setOpen) return ( - !v && close()}> + void setProjectLangs: (langs: LookupLangs | null) => void - close: () => void + /** Matches the `setOpen(boolean)` convention shared by the other disclosure + * stores (concordance, add-term, find-replace, …). */ + setOpen: (open: boolean) => void } export const useQuickLookupStore = create((set, get) => ({ @@ -36,5 +38,6 @@ export const useQuickLookupStore = create((set, get) => ({ }) }, setProjectLangs: (projectLangs) => set({ projectLangs }), - close: () => set({ open: false, prefill: '' }), + setOpen: (open) => set(open ? { open } : { open: false, prefill: '' }), })) + From 37b7e14e1cf1e62f3096749d2af140548c2d8f95 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 01:03:11 +0000 Subject: [PATCH 5/8] =?UTF-8?q?Phase=20E1=20=E2=80=94=20tooling:=20ESLint,?= =?UTF-8?q?=20Prettier=20config,=20knip,=20hygiene=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the standards scaffolding the repo lacked (there was no linter). Tooling - ESLint flat config (typescript-eslint + react-hooks + react-refresh); `pnpm lint` wired into CI. First run was clean — 3 errors fixed (prefer-const in syncSession; two intentional XML control-char regexes get precise inline disables) and 2 dead eslint-disable directives removed; the rest are warnings (react-refresh / exhaustive-deps) that don't gate CI. `no-explicit-any` is `warn` for now (tightened in Phase F). - Prettier config (.prettierrc/.prettierignore) + format scripts. The actual repo-wide reformat lands as its own isolated commit (E2) so it doesn't bury logic changes. - knip config + `pnpm knip` for dead-code detection (non-blocking; the geist / dictionary-* / coverage deps are build-time/CSS uses it can't see statically). - .editorconfig + .nvmrc (node 20, matching CI). Dead code removed (found by knip, each verified to have no importers) - src/components/ui/tooltip.tsx (+ the now-unused @radix-ui/react-tooltip dep) - src/features/dictionary/index.tsx (orphaned — no route or importer) - src/storage/schemas.ts (unused type re-export barrel) - the unused `jimp` devDependency (generate-icons uses sharp) Hygiene - CONTRIBUTING.md, SECURITY.md, PR template, bug/feature issue templates. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013kzE8hPSJxu19tzs7kuMMa --- .editorconfig | 16 + .github/ISSUE_TEMPLATE/bug_report.md | 27 + .github/ISSUE_TEMPLATE/feature_request.md | 20 + .github/PULL_REQUEST_TEMPLATE.md | 23 + .github/workflows/ci.yml | 1 + .nvmrc | 1 + .prettierignore | 8 + .prettierrc | 8 + CONTRIBUTING.md | 59 + SECURITY.md | 34 + eslint.config.js | 52 + knip.json | 25 + package.json | 15 +- pnpm-lock.yaml | 1816 ++++++++++------- src/components/ui/tooltip.tsx | 31 - src/core/glossary/tbx.ts | 2 + src/core/xml/escape.ts | 4 +- src/features/dictionary/index.tsx | 9 - .../editor/rich/RichSegmentEditor.tsx | 2 +- .../editor/rich/TrackedChangesPlugin.tsx | 2 +- src/storage/schemas.ts | 1 - src/storage/sync/syncSession.ts | 2 +- 22 files changed, 1380 insertions(+), 778 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 eslint.config.js create mode 100644 knip.json delete mode 100644 src/components/ui/tooltip.tsx delete mode 100644 src/features/dictionary/index.tsx delete mode 100644 src/storage/schemas.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ccaced5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig — https://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d139126 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Something isn't working as expected +title: '' +labels: bug +assignees: '' +--- + +**What happened** +A clear description of the bug. + +**Steps to reproduce** +1. +2. +3. + +**Expected behaviour** +What you expected to happen instead. + +**Environment** +- Build SHA (from the About page): +- Browser / OS: +- Mode: local-only / signed-in + +**Notes** +Screenshots, console errors, or anything else useful. Please do **not** paste +confidential source text. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..245c5e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for Verbalis +title: '' +labels: enhancement +assignees: '' +--- + +**The problem** +What are you trying to do that Verbalis makes hard today? + +**Proposed solution** +What you'd like to see. Keep in mind Verbalis is local-first and privacy-first — +features should respect that (nothing leaves the device unless the user asks). + +**Alternatives considered** +Any workarounds or other approaches. + +**Notes** +Anything else — prior art in memoQ / Trados / OmegaT, mockups, etc. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..eb6eddd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +## Summary + + + +## Changes + + + +- + +## Testing + + + +- [ ] `pnpm typecheck` +- [ ] `pnpm lint` +- [ ] `pnpm test:unit` +- [ ] `pnpm test:e2e` (for UI-facing changes) +- [ ] `pnpm build` + +## Notes + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64d7fc9..4b4e6ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,7 @@ jobs: - run: pnpm install - run: pnpm generate-icons - run: pnpm typecheck + - run: pnpm lint - run: pnpm test:unit - run: pnpm build # Regression guard for the cloud path (ROADMAP §3.1): build once with the diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..aa70a3f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,8 @@ +dist +dev-dist +coverage +node_modules +pnpm-lock.yaml +src-tauri/target +public/dictionaries +public/corpora diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..6c900fd --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "always" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f795a4f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing to Verbalis + +Thanks for your interest in Verbalis. A few things to know before you start. + +## License context + +Verbalis is **source-available, not open source** (see [`LICENSE.md`](LICENSE.md)). +The source is here so you can read it, self-host it, and use it as an individual +for free. Contributions are welcome, but by opening a pull request you agree that +your contribution is licensed under the same terms and that the maintainer may +relicense it as part of the project. For anything substantial, please open an +issue to discuss first. + +## Development + +Verbalis is a local-first React + TypeScript + Vite app. It runs 100% locally +with no account or backend. + +```bash +pnpm install +pnpm dev # start the dev server +pnpm test:unit # unit tests (vitest) +pnpm test:e2e # end-to-end (playwright) +pnpm typecheck # tsc, no emit +pnpm lint # eslint +pnpm format:check # prettier check +pnpm build # production build +``` + +- **Node** 20 (see `.nvmrc`) and **pnpm** (pinned via `packageManager` in + `package.json`). +- The optional signed-in mode is behind `VITE_SUPABASE_*` — leave it unset to + develop the local-only path. See [`docs/cloud.md`](docs/cloud.md). + +## Before you open a PR + +Every change must be green: + +```bash +pnpm typecheck && pnpm lint && pnpm test:unit && pnpm build +``` + +Run `pnpm test:e2e` too for anything UI-facing. CI runs all of these. + +## Conventions + +- **Keep `core/*` pure.** Business logic goes in `src/core` as pure functions + with injected I/O (`fetchImpl`, clients); push side effects to the edges. This + is why the test suite is strong — keep it that way. +- **The optional cloud must tree-shake to zero** when unconfigured. Load Supabase + only through the dynamic `getSupabase()` path; never import + `@supabase/supabase-js` statically. +- **Match the surrounding code** — its naming, comment density, and idioms. +- Architecture notes live in [`docs/architecture.md`](docs/architecture.md). + +## Reporting bugs / requesting features + +Use the issue templates. For anything security-related, follow +[`SECURITY.md`](SECURITY.md) instead of opening a public issue. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..92d84aa --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ +# Security Policy + +Verbalis is a privacy-first, local-first CAT tool: by design, your files, +translation memory and glossaries stay in your browser. Security reports are +taken seriously. + +## Reporting a vulnerability + +**Please do not open a public issue for security vulnerabilities.** + +Instead, email **pedrobritx@gmail.com** with: + +- a description of the issue and its impact, +- steps to reproduce (a proof of concept if you have one), +- the affected version / build (the About page shows the build SHA). + +You can expect an acknowledgement within a few days. Please give a reasonable +window to investigate and ship a fix before any public disclosure. + +## Scope + +Especially relevant for this project: + +- anything that could cause user data (source text, TM, glossaries) to **leave + the device** unexpectedly in local-only mode, +- issues in the optional signed-in mode (Supabase auth, RLS, cloud sync, + collaboration) that could expose one user's data to another, +- the storage-connector OAuth flows (Google Drive, OneDrive). + +## Out of scope + +- Vulnerabilities in third-party dependencies without a demonstrated exploit in + Verbalis (report those upstream). +- Anything requiring physical access to an unlocked device. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..bbfee19 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,52 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { + ignores: [ + 'dist', + 'dev-dist', + 'coverage', + 'node_modules', + 'src-tauri', + 'scripts', + '*.config.{js,ts}', + 'playwright.config.ts', + 'vitest.config.ts', + ], + }, + { + files: ['**/*.{ts,tsx}'], + extends: [js.configs.recommended, ...tseslint.configs.recommended], + languageOptions: { + ecmaVersion: 2022, + globals: { ...globals.browser, ...globals.node }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + // Tightened incrementally (see docs/refactor-audit.md) — warn for now so + // the linter can land without a codebase-wide sweep in the same change. + '@typescript-eslint/no-explicit-any': 'warn', + }, + }, + { + // Tests lean on jsdom/vitest globals and some intentional escapes. + files: ['tests/**/*.{ts,tsx}'], + languageOptions: { globals: { ...globals.node } }, + rules: { + '@typescript-eslint/no-explicit-any': 'off', + }, + }, +) diff --git a/knip.json b/knip.json new file mode 100644 index 0000000..2e0d809 --- /dev/null +++ b/knip.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://unpkg.com/knip@5/schema.json", + "entry": [ + "src/main.tsx", + "src/workers/*.worker.ts", + "index.html", + "vite.config.ts", + "playwright.config.ts", + "vitest.config.ts", + "eslint.config.js", + "scripts/*.mjs", + "tests/**/*.{test,spec}.{ts,tsx}" + ], + "project": ["src/**/*.{ts,tsx}"], + "ignore": ["src/vite-env.d.ts"], + "ignoreBinaries": ["tauri"], + "ignoreDependencies": [ + "geist", + "dictionary-en", + "dictionary-en-gb", + "dictionary-pt", + "dictionary-pt-pt", + "@vitest/coverage-v8" + ] +} diff --git a/package.json b/package.json index 674ab7d..6ad1d37 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,11 @@ "test:unit": "vitest run", "test:e2e": "playwright test", "typecheck": "tsc -b", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "knip": "knip", "generate-icons": "node scripts/generate-icons.mjs", "build-corpora": "node scripts/build-corpora.mjs", "build-dictionaries": "node scripts/build-dictionaries.mjs", @@ -23,7 +28,6 @@ "@lexical/react": "^0.45.0", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-tooltip": "^1.2.8", "@supabase/supabase-js": "^2.110.7", "@tanstack/react-query": "^5.62.0", "@xenova/transformers": "^2.17.2", @@ -63,6 +67,7 @@ ] }, "devDependencies": { + "@eslint/js": "^9.17.0", "@playwright/test": "^1.49.0", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.1.0", @@ -74,12 +79,18 @@ "@vitejs/plugin-react": "^4.3.4", "@vitest/coverage-v8": "^3.2.4", "autoprefixer": "^10.4.20", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", + "knip": "^5.41.0", + "prettier": "^3.4.2", + "typescript-eslint": "^8.18.0", "dictionary-en": "^4.0.0", "dictionary-en-gb": "^3.0.0", "dictionary-pt": "^4.0.0", "dictionary-pt-pt": "^2.0.0", "fake-indexeddb": "^6.2.5", - "jimp": "^1.6.0", "jsdom": "^29.1.1", "jszip": "^3.10.1", "postcss": "^8.4.49", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c7dfac..fa107b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,9 +20,6 @@ importers: '@radix-ui/react-slot': specifier: ^1.2.4 version: 1.2.4(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-tooltip': - specifier: ^1.2.8 - version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@supabase/supabase-js': specifier: ^2.110.7 version: 2.110.7 @@ -117,6 +114,9 @@ importers: specifier: ^5.0.2 version: 5.0.13(@types/react@18.3.29)(react@18.3.1) devDependencies: + '@eslint/js': + specifier: ^9.17.0 + version: 9.39.5 '@playwright/test': specifier: ^1.49.0 version: 1.60.0 @@ -143,10 +143,10 @@ importers: version: 1.0.5 '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.7.0(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)) + version: 4.7.0(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) '@vitest/coverage-v8': specifier: ^3.2.4 - version: 3.2.4(supports-color@7.2.0)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0)) + version: 3.2.4(supports-color@7.2.0)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0)(yaml@2.9.0)) autoprefixer: specifier: ^10.4.20 version: 10.5.0(postcss@8.5.15) @@ -162,39 +162,57 @@ importers: dictionary-pt-pt: specifier: ^2.0.0 version: 2.0.0 + eslint: + specifier: ^9.17.0 + version: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint-plugin-react-hooks: + specifier: ^5.1.0 + version: 5.2.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) + eslint-plugin-react-refresh: + specifier: ^0.4.16 + version: 0.4.26(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) fake-indexeddb: specifier: ^6.2.5 version: 6.2.5 - jimp: - specifier: ^1.6.0 - version: 1.6.1(supports-color@7.2.0) + globals: + specifier: ^15.14.0 + version: 15.15.0 jsdom: specifier: ^29.1.1 version: 29.1.1 jszip: specifier: ^3.10.1 version: 3.10.1 + knip: + specifier: ^5.41.0 + version: 5.88.1(@types/node@25.9.5)(typescript@5.9.3) postcss: specifier: ^8.4.49 version: 8.5.15 + prettier: + specifier: ^3.4.2 + version: 3.9.5 sharp: specifier: ^0.35.1 version: 0.35.1 tailwindcss: specifier: ^3.4.17 - version: 3.4.19 + version: 3.4.19(yaml@2.9.0) typescript: specifier: ^5.7.2 version: 5.9.3 + typescript-eslint: + specifier: ^8.18.0 + version: 8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) vite: specifier: ^6.0.5 - version: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0) + version: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) vite-plugin-pwa: specifier: ^0.21.1 - version: 0.21.2(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0))(workbox-build@7.4.1(@types/babel__core@7.20.5)(supports-color@7.2.0))(workbox-window@7.4.1) + version: 0.21.2(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0))(workbox-build@7.4.1(@types/babel__core@7.20.5)(supports-color@7.2.0))(workbox-window@7.4.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0) + version: 3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0)(yaml@2.9.0) packages: @@ -755,9 +773,6 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@borewit/text-codec@0.2.2': - resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} - '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true @@ -798,9 +813,18 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -957,6 +981,44 @@ packages: cpu: [x64] os: [win32] + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.6': + resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.15.1': resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -991,6 +1053,26 @@ packages: resolution: {integrity: sha512-/KPde26khDUIPkTGU82jdtTW9UAuvUTumCAbFs/7giR0SxsvZC4hru51PBvpijH6BVkHcROcvZM/lpy5h1jRRA==} engines: {node: '>=18'} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} @@ -1314,118 +1396,6 @@ packages: resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} - '@jimp/core@1.6.1': - resolution: {integrity: sha512-+BoKC5G6hkrSy501zcJ2EpfnllP+avPevcBfRcZe/CW+EwEfY6X1EZ8QWyT7NpDIvEEJb1fdJnMMfUnFkxmw9A==} - engines: {node: '>=18'} - - '@jimp/diff@1.6.1': - resolution: {integrity: sha512-YkKDPdHjLgo1Api3+Bhc0GLAygldlpt97NfOKoNg1U6IUNXA6X2MgosCjPfSBiSvJvrrz1fsIR+/4cfYXBI/HQ==} - engines: {node: '>=18'} - - '@jimp/file-ops@1.6.1': - resolution: {integrity: sha512-T+gX6osHjprbDRad0/B71Evyre7ZdVY1z/gFGEG9Z8KOtZPKboWvPeP2UjbZYWQLy9UKCPQX1FNAnDiOPkJL7w==} - engines: {node: '>=18'} - - '@jimp/js-bmp@1.6.1': - resolution: {integrity: sha512-xzWzNT4/u5zGrTT3Tme9sGU7YzIKxi13+BCQwLqACbt5DXf9SAfdzRkopZQnmDko+6In5nqaT89Gjs43/WdnYQ==} - engines: {node: '>=18'} - - '@jimp/js-gif@1.6.1': - resolution: {integrity: sha512-YjY2W26rQa05XhanYhRZ7dingCiNN+T2Ymb1JiigIbABY0B28wHE3v3Cf1/HZPWGu0hOg36ylaKgV5KxF2M58w==} - engines: {node: '>=18'} - - '@jimp/js-jpeg@1.6.1': - resolution: {integrity: sha512-HT9H3yOmlOFzYmdI15IYdfy6ggQhSRIaHeA+OTJSEORXBqEo97sUZu/DsgHIcX5NJ7TkJBTgZ9BZXsV6UbsyMg==} - engines: {node: '>=18'} - - '@jimp/js-png@1.6.1': - resolution: {integrity: sha512-SZ/KVhI5UjcSzzlXsXdIi/LhJ7UShf2NkMOtVrbZQcGzsqNtynAelrOXeoTxcanfVqmNhAoVHg8yR2cYoqrYjA==} - engines: {node: '>=18'} - - '@jimp/js-tiff@1.6.1': - resolution: {integrity: sha512-jDG/eJquID1M4MBlKMmDRBmz2TpXMv7TUyu2nIRUxhlUc2ogC82T+VQUkca9GJH1BBJ9dx5sSE5dGkWNjIbZxw==} - engines: {node: '>=18'} - - '@jimp/plugin-blit@1.6.1': - resolution: {integrity: sha512-MwnI7C7K81uWddY9FLw1fCOIy6SsPIUftUz36Spt7jisCn8/40DhQMlSxpxTNelnZb/2SnloFimQfRZAmHLOqQ==} - engines: {node: '>=18'} - - '@jimp/plugin-blur@1.6.1': - resolution: {integrity: sha512-lIo7Tzp5jQu30EFFSK/phXANK3citKVEjepDjQ6ljHoIFtuMRrnybnmI2Md24ulvWlDaz+hh3n6qrMb8ydwhZQ==} - engines: {node: '>=18'} - - '@jimp/plugin-circle@1.6.1': - resolution: {integrity: sha512-kK1PavY6cKHNNKce37vdV4Tmpc1/zDKngGoeOV3j+EMatoHFZUinV3s6F9aWryPs3A0xhCLZgdJ6Zeea1d5LCQ==} - engines: {node: '>=18'} - - '@jimp/plugin-color@1.6.1': - resolution: {integrity: sha512-LtUN1vAP+LRlZAtTNVhDRSiXx+26Kbz3zJaG6a5k59gQ95jgT5mknnF8lxkHcqJthM4MEk3/tPxkdJpEybyF/A==} - engines: {node: '>=18'} - - '@jimp/plugin-contain@1.6.1': - resolution: {integrity: sha512-m0qhrfA8jkTqretGv4w+T/ADFR4GwBpE0sCOC2uJ0dzr44/ddOMsIdrpi89kabqYiPYIrxkgdCVCLm3zn1Vkkg==} - engines: {node: '>=18'} - - '@jimp/plugin-cover@1.6.1': - resolution: {integrity: sha512-hZytnsth0zoll6cPf434BrT+p/v569Wr5tyO6Dp0dH1IDPhzhB5F38sZGMLDo7bzQiN9JFVB3fxkcJ/WYCJ3Mg==} - engines: {node: '>=18'} - - '@jimp/plugin-crop@1.6.1': - resolution: {integrity: sha512-EerRSLlclXyKDnYc/H9w/1amZW7b7v3OGi/VlerPd2M/pAu5X8TkyYWtfqYCXnNp1Ixtd8oCo9zGfY9zoXT4rg==} - engines: {node: '>=18'} - - '@jimp/plugin-displace@1.6.1': - resolution: {integrity: sha512-K07QVl7xQwIfD6KfxRV/c3E9e7ZBXxUXdWuvoTWcKHL2qV48MOF5Nqbz/aJW4ThnQARIsxvYlZjPFiqkCjlU+g==} - engines: {node: '>=18'} - - '@jimp/plugin-dither@1.6.1': - resolution: {integrity: sha512-+2V+GCV2WycMoX1/z977TkZ8Zq/4MVSKElHYatgUqtwXMi2fDK2gKYU2g9V39IqFvTJsTIsK0+58VFz/ROBVew==} - engines: {node: '>=18'} - - '@jimp/plugin-fisheye@1.6.1': - resolution: {integrity: sha512-XtS5ZyoZ0vxZxJ6gkqI63SivhtI58vX95foMPM+cyzYkRsJXMOYCr8DScxF5bp4Xr003NjYm/P+7+08tibwzHA==} - engines: {node: '>=18'} - - '@jimp/plugin-flip@1.6.1': - resolution: {integrity: sha512-ws38W/sGj7LobNRayQ83garxiktOyWxM5vO/y4a/2cy9v65SLEUzVkrj+oeAaUSSObdz4HcCEla7XtGlnAGAaA==} - engines: {node: '>=18'} - - '@jimp/plugin-hash@1.6.1': - resolution: {integrity: sha512-sZt6ZcMX6i8vFWb4GYnw0pR/o9++ef0dTVcboTB5B/g7nrxCODIB4wfEkJ/YqZM5wUvol77K1qeS0/rVO6z21A==} - engines: {node: '>=18'} - - '@jimp/plugin-mask@1.6.1': - resolution: {integrity: sha512-SIG0/FcmEj3tkwFxc7fAGLO8o4uNzMpSOdQOhbCgxefQKq5wOVMk9BQx/sdMPBwtMLr9WLq0GzLA/rk6t2v20A==} - engines: {node: '>=18'} - - '@jimp/plugin-print@1.6.1': - resolution: {integrity: sha512-BYVz/X3Xzv8XYilVeDy11NOp0h7BTDjlOtu0BekIFHP1yHVd24AXNzbOy52XlzYZWQ0Dl36HOHEpl/nSNrzc6w==} - engines: {node: '>=18'} - - '@jimp/plugin-quantize@1.6.1': - resolution: {integrity: sha512-J2En9PLURfP+vwYDtuZ9T8yBW6BWYZBScydAjRiPBmJfEhTcNQqiiQODrZf7EqbbX/Sy5H6dAeRiqkgoV9N6Ww==} - engines: {node: '>=18'} - - '@jimp/plugin-resize@1.6.1': - resolution: {integrity: sha512-CLkrtJoIz2HdWnpYiN6p8KYcPc00rCH/SUu6o+lfZL05Q4uhecJlnvXuj9x+U6mDn3ldPmJj6aZqMHuUJzdVqg==} - engines: {node: '>=18'} - - '@jimp/plugin-rotate@1.6.1': - resolution: {integrity: sha512-nOjVjbbj705B02ksysKnh0POAwEBXZtJ9zQ5qC+X7Tavl3JNn+P3BzQovbBxLPSbUSld6XID9z5ijin4PtOAUg==} - engines: {node: '>=18'} - - '@jimp/plugin-threshold@1.6.1': - resolution: {integrity: sha512-JOKv9F8s6tnVLf4sB/2fF0F339EFnHvgEdFYugO6VhowKLsap0pEZmLyE/DlRnYtIj2RddHZVxVMp/eKJ04l2Q==} - engines: {node: '>=18'} - - '@jimp/types@1.6.1': - resolution: {integrity: sha512-leI7YbveTNi565m910XgIOwXyuu074H5qazAD1357HImJSv2hqxnWXpwxQbadGWZ7goZRYBDZy5lpqud0p7q5w==} - engines: {node: '>=18'} - - '@jimp/utils@1.6.1': - resolution: {integrity: sha512-veFPRd93FCnS7AgmCkPgARVGoDRrJ9cm1ujuNyA+UfQ5VKbED2002sm5XfFLFwTsKC8j04heTrwe+tU1dluXOw==} - engines: {node: '>=18'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1523,6 +1493,12 @@ packages: peerDependencies: yjs: '>=13.5.22' + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@next/env@16.2.6': resolution: {integrity: sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==} @@ -1590,6 +1566,109 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.24.2': + resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.24.2': + resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==} + cpu: [x64] + os: [win32] + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1635,19 +1714,6 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} - 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 - '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: @@ -1723,19 +1789,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} - 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 - '@radix-ui/react-portal@1.1.9': resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: @@ -1806,19 +1859,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-tooltip@1.2.8': - resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} - 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 - '@radix-ui/react-use-callback-ref@1.1.1': resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: @@ -1864,40 +1904,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} - 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 - - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@remix-run/router@1.23.2': resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} engines: {node: '>=14.0.0'} @@ -2159,17 +2165,13 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tokenizer/inflate@0.4.1': - resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} - engines: {node: '>=18'} - - '@tokenizer/token@0.3.0': - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1': resolution: {integrity: sha512-/67zpWDBLV+oYAEL682s1ktXL0HgqX76f6gaVGkGnVZlBbm1zd0v4Bz8MFF2GGhoX9rvfq3KSQHubFHwa6w6/Q==} engines: {node: '>=12'} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -2200,6 +2202,9 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/long@4.0.2': resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} @@ -2238,6 +2243,65 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@typescript-eslint/eslint-plugin@8.65.0': + resolution: {integrity: sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.65.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.65.0': + resolution: {integrity: sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.65.0': + resolution: {integrity: sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.65.0': + resolution: {integrity: sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.65.0': + resolution: {integrity: sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.65.0': + resolution: {integrity: sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.65.0': + resolution: {integrity: sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.65.0': + resolution: {integrity: sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.65.0': + resolution: {integrity: sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.65.0': + resolution: {integrity: sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitejs/plugin-react@4.7.0': resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2289,11 +2353,19 @@ packages: resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} @@ -2317,9 +2389,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - any-base@1.1.0: - resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} - any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -2333,6 +2402,9 @@ packages: argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} @@ -2377,10 +2449,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - await-to-js@3.0.0: - resolution: {integrity: sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==} - engines: {node: '>=6.0.0'} - b4a@1.8.1: resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} peerDependencies: @@ -2476,8 +2544,8 @@ packages: bluebird@3.4.7: resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} - bmp-ts@1.0.9: - resolution: {integrity: sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} @@ -2517,6 +2585,10 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -2531,6 +2603,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} @@ -2589,6 +2665,9 @@ packages: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -2667,6 +2746,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -2830,6 +2912,59 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -2847,9 +2982,6 @@ packages: events-universal@1.0.1: resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} - exif-parser@0.1.12: - resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} - expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} @@ -2878,12 +3010,18 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.2: resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fd-package-json@2.0.0: + resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2893,9 +3031,9 @@ packages: picomatch: optional: true - file-type@21.3.4: - resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==} - engines: {node: '>=20'} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} filelist@1.0.6: resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} @@ -2904,9 +3042,20 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flatbuffers@1.12.0: resolution: {integrity: sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -2915,6 +3064,11 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + formatly@0.3.0: + resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} + engines: {node: '>=18.3.0'} + hasBin: true + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -2981,9 +3135,6 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - gifwrap@0.10.1: - resolution: {integrity: sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==} - github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -3006,6 +3157,14 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -3070,12 +3229,25 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - image-q@4.0.0: - resolution: {integrity: sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + engines: {node: '>= 4'} immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -3271,16 +3443,13 @@ packages: engines: {node: '>=10'} hasBin: true - jimp@1.6.1: - resolution: {integrity: sha512-hNQh6rZtWfSVWSNVmvq87N5BPJsNH7k7I7qyrXf9DOma9xATQk3fsyHazCQe51nCjdkoWdTmh0vD7bjVSLoxxw==} - engines: {node: '>=18'} - jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true - jpeg-js@0.4.4: - resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} @@ -3291,6 +3460,10 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + hasBin: true + jsdom@29.1.1: resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} @@ -3305,9 +3478,18 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3323,6 +3505,17 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + knip@5.88.1: + resolution: {integrity: sha512-tpy5o7zu1MjawVkLPuahymVJekYY3kYjvzcoInhIchgePxTlo+api90tBv2KfhAIe5uXh+mez1tAfmbv8/TiZg==} + engines: {node: '>=18.18.0'} + hasBin: true + peerDependencies: + '@types/node': '>=18' + typescript: '>=5.0.4 <7' + launder@1.7.1: resolution: {integrity: sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw==} @@ -3330,6 +3523,10 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lexical@0.45.0: resolution: {integrity: sha512-z2M9C2ILPW7SopQE1aKbOFdZJbe3HvsrgWnJKveMDrSEvVjU9Hce4UpKVsAdkwY7TgY9RDuCV3+sfrGu0WkL+w==} @@ -3348,9 +3545,16 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} @@ -3539,11 +3743,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -3559,6 +3758,9 @@ packages: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@5.1.9: resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} @@ -3599,6 +3801,9 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next@16.2.6: resolution: {integrity: sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==} engines: {node: '>=20.9.0'} @@ -3658,9 +3863,6 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} - omggif@1.0.10: - resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} - once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3680,24 +3882,34 @@ packages: option@0.2.4: resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-resolver@11.24.2: + resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - parse-bmfont-ascii@1.0.6: - resolution: {integrity: sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==} - - parse-bmfont-binary@1.0.6: - resolution: {integrity: sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==} - - parse-bmfont-xml@1.1.6: - resolution: {integrity: sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} parse-srcset@1.0.2: resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} @@ -3705,6 +3917,10 @@ packages: parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -3750,10 +3966,6 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - pixelmatch@5.3.0: - resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} - hasBin: true - platform@1.3.6: resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} @@ -3767,14 +3979,6 @@ packages: engines: {node: '>=18'} hasBin: true - pngjs@6.0.0: - resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} - engines: {node: '>=12.13.0'} - - pngjs@7.0.0: - resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} - engines: {node: '>=14.19.0'} - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3836,6 +4040,15 @@ packages: deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. hasBin: true + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.9.5: + resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} + engines: {node: '>=14'} + hasBin: true + pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} @@ -3990,6 +4203,10 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve@1.22.12: resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} @@ -4124,14 +4341,14 @@ packages: simple-swizzle@0.2.4: resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} - simple-xml-to-json@1.2.7: - resolution: {integrity: sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==} - engines: {node: '>=20.12.2'} - smob@1.6.2: resolution: {integrity: sha512-RQsvleCbF8cVHEv+xuDGaA4pOizFqJ0GgjtMSRo6oP8pnN7WsigHgVGey6aILRBKv4W2YOMHLqbKdnB6hpB9fw==} engines: {node: '>=20.0.0'} + smol-toml@1.7.0: + resolution: {integrity: sha512-aqVvWoyO21L23mb+drl4RmMXbf6N7FdHjAhTRA9ZBL7apWBgfWC16KjrASI+1p9GAroljyMHj6fK67i0UiTNvQ==} + engines: {node: '>= 18'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -4215,13 +4432,17 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - strtok3@10.3.5: - resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} - engines: {node: '>=18'} - styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -4308,9 +4529,6 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -4346,10 +4564,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - token-types@6.1.2: - resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} - engines: {node: '>=14.16'} - tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -4364,6 +4578,12 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -4373,6 +4593,10 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -4393,14 +4617,21 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typescript-eslint@8.65.0: + resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true - uint8array-extras@1.5.0: - resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} - engines: {node: '>=18'} + unbash@2.2.0: + resolution: {integrity: sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w==} + engines: {node: '>=14'} unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} @@ -4465,6 +4696,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -4485,9 +4719,6 @@ packages: '@types/react': optional: true - utif2@4.1.0: - resolution: {integrity: sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==} - util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4586,6 +4817,10 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + walk-up-path@4.0.0: + resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==} + engines: {node: 20 || >=22} + webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} @@ -4630,6 +4865,10 @@ packages: engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + workbox-background-sync@7.4.1: resolution: {integrity: sha512-HhT7KE8tOWDm02wRNshXUnUPofMlhenF2DBdUnDPOubhizzPeItkYTmAB6td1Z2cjYPa98vzEiPLEuzn5hN66g==} @@ -4698,13 +4937,6 @@ packages: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} - xml-parse-from-string@1.0.1: - resolution: {integrity: sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==} - - xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} - xml@1.0.1: resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} @@ -4712,10 +4944,6 @@ packages: resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} engines: {node: '>=4.0'} - xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} - xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} @@ -4728,12 +4956,21 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yjs@13.6.31: resolution: {integrity: sha512-Eq+5BRfbeGyqGVrTJL3bEcr8gKkxPuyuoHmAwpk52fDb8kOVMrfVSTRPd6yiGgX5Fskb96qCRjzjbRjrL4YEnw==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} zustand@5.0.13: resolution: {integrity: sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==} @@ -5474,8 +5711,6 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@borewit/text-codec@0.2.2': {} - '@bramus/specificity@2.4.2': dependencies: css-tree: 3.2.1 @@ -5504,11 +5739,27 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -5587,6 +5838,52 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))': + dependencies: + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2(supports-color@7.2.0)': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.6(supports-color@7.2.0)': + dependencies: + ajv: 6.15.0 + debug: 4.4.3(supports-color@7.2.0) + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.5': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@exodus/bytes@1.15.1': {} '@floating-ui/core@1.7.5': @@ -5616,6 +5913,22 @@ snapshots: '@huggingface/jinja@0.2.2': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@img/colour@1.1.0': {} '@img/sharp-darwin-arm64@0.34.5': @@ -5785,7 +6098,7 @@ snapshots: '@img/sharp-wasm32@0.34.5': dependencies: - '@emnapi/runtime': 1.11.1 + '@emnapi/runtime': 1.11.2 optional: true '@img/sharp-wasm32@0.35.1': @@ -5829,229 +6142,6 @@ snapshots: '@istanbuljs/schema@0.1.6': {} - '@jimp/core@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/file-ops': 1.6.1 - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - await-to-js: 3.0.0 - exif-parser: 0.1.12 - file-type: 21.3.4(supports-color@7.2.0) - mime: 3.0.0 - transitivePeerDependencies: - - supports-color - - '@jimp/diff@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/plugin-resize': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - pixelmatch: 5.3.0 - transitivePeerDependencies: - - supports-color - - '@jimp/file-ops@1.6.1': {} - - '@jimp/js-bmp@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - bmp-ts: 1.0.9 - transitivePeerDependencies: - - supports-color - - '@jimp/js-gif@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - gifwrap: 0.10.1 - omggif: 1.0.10 - transitivePeerDependencies: - - supports-color - - '@jimp/js-jpeg@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - jpeg-js: 0.4.4 - transitivePeerDependencies: - - supports-color - - '@jimp/js-png@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - pngjs: 7.0.0 - transitivePeerDependencies: - - supports-color - - '@jimp/js-tiff@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - utif2: 4.1.0 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-blit@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - zod: 3.25.76 - - '@jimp/plugin-blur@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/utils': 1.6.1 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-circle@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - zod: 3.25.76 - - '@jimp/plugin-color@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - tinycolor2: 1.6.0 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-contain@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-blit': 1.6.1 - '@jimp/plugin-resize': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-cover@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-crop': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-resize': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-crop@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-displace@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - zod: 3.25.76 - - '@jimp/plugin-dither@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - - '@jimp/plugin-fisheye@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - zod: 3.25.76 - - '@jimp/plugin-flip@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - zod: 3.25.76 - - '@jimp/plugin-hash@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/js-bmp': 1.6.1(supports-color@7.2.0) - '@jimp/js-jpeg': 1.6.1(supports-color@7.2.0) - '@jimp/js-png': 1.6.1(supports-color@7.2.0) - '@jimp/js-tiff': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-color': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-resize': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - any-base: 1.1.0 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-mask@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - zod: 3.25.76 - - '@jimp/plugin-print@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/js-jpeg': 1.6.1(supports-color@7.2.0) - '@jimp/js-png': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-blit': 1.6.1 - '@jimp/types': 1.6.1 - parse-bmfont-ascii: 1.0.6 - parse-bmfont-binary: 1.0.6 - parse-bmfont-xml: 1.1.6 - simple-xml-to-json: 1.2.7 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-quantize@1.6.1': - dependencies: - image-q: 4.0.0 - zod: 3.25.76 - - '@jimp/plugin-resize@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-rotate@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-crop': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-resize': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/plugin-threshold@1.6.1(supports-color@7.2.0)': - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-color': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-hash': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color - - '@jimp/types@1.6.1': - dependencies: - zod: 3.25.76 - - '@jimp/utils@1.6.1': - dependencies: - '@jimp/types': 1.6.1 - tinycolor2: 1.6.0 - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -6254,6 +6344,13 @@ snapshots: lexical: 0.45.0 yjs: 13.6.31 + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@next/env@16.2.6': {} '@next/swc-darwin-arm64@16.2.6': @@ -6292,6 +6389,67 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + optional: true + + '@oxc-resolver/binding-android-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + optional: true + '@pkgjs/parseargs@0.11.0': optional: true @@ -6317,22 +6475,13 @@ snapshots: '@protobufjs/inquire@1.1.2': {} - '@protobufjs/path@1.1.2': {} - - '@protobufjs/pool@1.1.0': {} - - '@protobufjs/utf8@1.1.1': {} - - '@radix-ui/primitive@1.1.3': {} - - '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.29 - '@types/react-dom': 18.3.7(@types/react@18.3.29) + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.29)(react@18.3.1)': dependencies: @@ -6405,24 +6554,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.29 - '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/react-dom': 2.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/rect': 1.1.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.29 - '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -6475,26 +6606,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.29 - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.29)(react@18.3.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.29 - '@types/react-dom': 18.3.7(@types/react@18.3.29) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.29)(react@18.3.1)': dependencies: react: 18.3.1 @@ -6529,31 +6640,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.29 - '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.29)(react@18.3.1)': - dependencies: - '@radix-ui/rect': 1.1.1 - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.29 - - '@radix-ui/react-use-size@1.1.1(@types/react@18.3.29)(react@18.3.1)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.29)(react@18.3.1) - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.29 - - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.29))(@types/react@18.3.29)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.29 - '@types/react-dom': 18.3.7(@types/react@18.3.29) - - '@radix-ui/rect@1.1.1': {} - '@remix-run/router@1.23.2': {} '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -6754,15 +6840,6 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 - '@tokenizer/inflate@0.4.1(supports-color@7.2.0)': - dependencies: - debug: 4.4.3(supports-color@7.2.0) - token-types: 6.1.2 - transitivePeerDependencies: - - supports-color - - '@tokenizer/token@0.3.0': {} - '@trickfilm400/rollup-plugin-off-main-thread@3.0.0-pre1': dependencies: ejs: 3.1.10 @@ -6770,6 +6847,11 @@ snapshots: magic-string: 0.30.21 string.prototype.matchall: 4.0.12 + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -6808,6 +6890,8 @@ snapshots: '@types/estree@1.0.9': {} + '@types/json-schema@7.0.15': {} + '@types/long@4.0.2': {} '@types/mdast@4.0.4': @@ -6841,7 +6925,98 @@ snapshots: '@types/unist@3.0.3': {} - '@vitejs/plugin-react@4.7.0(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0))': + '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.65.0 + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + ignore: 7.0.6 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.65.0 + debug: 4.4.3(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.65.0(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 + debug: 4.4.3(supports-color@7.2.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.65.0': + dependencies: + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 + + '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + debug: 4.4.3(supports-color@7.2.0) + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.65.0': {} + + '@typescript-eslint/typescript-estree@8.65.0(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.65.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.9.3) + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/visitor-keys': 8.65.0 + debug: 4.4.3(supports-color@7.2.0) + minimatch: 10.2.5 + semver: 7.8.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) + '@typescript-eslint/scope-manager': 8.65.0 + '@typescript-eslint/types': 8.65.0 + '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@5.9.3) + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.65.0': + dependencies: + '@typescript-eslint/types': 8.65.0 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@4.7.0(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0(supports-color@7.2.0) '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0(supports-color@7.2.0)) @@ -6849,11 +7024,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0) + vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@3.2.4(supports-color@7.2.0)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0))': + '@vitest/coverage-v8@3.2.4(supports-color@7.2.0)(vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0)(yaml@2.9.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -6868,7 +7043,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0) + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0)(yaml@2.9.0) transitivePeerDependencies: - supports-color @@ -6880,13 +7055,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0))': + '@vitest/mocker@3.2.4(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0) + vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -6928,8 +7103,19 @@ snapshots: '@xmldom/xmldom@0.8.13': {} + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn@8.16.0: {} + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -6949,8 +7135,6 @@ snapshots: ansi-styles@6.2.3: {} - any-base@1.1.0: {} - any-promise@1.3.0: {} anymatch@3.1.3: @@ -6964,6 +7148,8 @@ snapshots: dependencies: sprintf-js: 1.0.3 + argparse@2.0.1: {} + aria-hidden@1.2.6: dependencies: tslib: 2.8.1 @@ -7014,8 +7200,6 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - await-to-js@3.0.0: {} - b4a@1.8.1: {} babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0(supports-color@7.2.0))(supports-color@7.2.0): @@ -7098,7 +7282,10 @@ snapshots: bluebird@3.4.7: {} - bmp-ts@1.0.9: {} + brace-expansion@1.1.16: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 brace-expansion@2.1.0: dependencies: @@ -7146,6 +7333,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + camelcase-css@2.0.1: {} caniuse-lite@1.0.30001793: {} @@ -7160,6 +7349,11 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + character-entities@2.0.2: {} check-error@2.1.3: {} @@ -7222,6 +7416,8 @@ snapshots: common-tags@1.8.2: {} + concat-map@0.0.1: {} + convert-source-map@2.0.0: {} core-js-compat@3.49.0: @@ -7296,6 +7492,8 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + deepmerge@4.3.1: {} define-data-property@1.1.4: @@ -7520,6 +7718,82 @@ snapshots: escape-string-regexp@5.0.0: {} + eslint-plugin-react-hooks@5.2.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)): + dependencies: + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + + eslint-plugin-react-refresh@0.4.26(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)): + dependencies: + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2(supports-color@7.2.0) + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.6(supports-color@7.2.0) + '@eslint/js': 9.39.5 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@7.2.0) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@2.0.2: {} estree-walker@3.0.3: @@ -7536,8 +7810,6 @@ snapshots: transitivePeerDependencies: - bare-abort-controller - exif-parser@0.1.12: {} - expand-template@2.0.3: {} expect-type@1.3.0: {} @@ -7560,24 +7832,25 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-levenshtein@2.0.6: {} + fast-uri@3.1.2: {} fastq@1.20.1: dependencies: reusify: 1.1.0 + fd-package-json@2.0.0: + dependencies: + walk-up-path: 4.0.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 - file-type@21.3.4(supports-color@7.2.0): + file-entry-cache@8.0.0: dependencies: - '@tokenizer/inflate': 0.4.1(supports-color@7.2.0) - strtok3: 10.3.5 - token-types: 6.1.2 - uint8array-extras: 1.5.0 - transitivePeerDependencies: - - supports-color + flat-cache: 4.0.1 filelist@1.0.6: dependencies: @@ -7587,8 +7860,20 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + flatbuffers@1.12.0: {} + flatted@3.4.2: {} + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -7598,6 +7883,10 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + formatly@0.3.0: + dependencies: + fd-package-json: 2.0.0 + fraction.js@5.3.4: {} fs-constants@1.0.0: {} @@ -7666,11 +7955,6 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - gifwrap@0.10.1: - dependencies: - image-q: 4.0.0 - omggif: 1.0.10 - github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -7699,6 +7983,10 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 2.0.2 + globals@14.0.0: {} + + globals@15.15.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -7758,12 +8046,19 @@ snapshots: ieee754@1.2.1: {} - image-q@4.0.0: - dependencies: - '@types/node': 16.9.1 + ignore@5.3.2: {} + + ignore@7.0.6: {} immediate@3.0.6: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + indent-string@4.0.0: {} inherits@2.0.4: {} @@ -7953,41 +8248,9 @@ snapshots: filelist: 1.0.6 picocolors: 1.1.1 - jimp@1.6.1(supports-color@7.2.0): - dependencies: - '@jimp/core': 1.6.1(supports-color@7.2.0) - '@jimp/diff': 1.6.1(supports-color@7.2.0) - '@jimp/js-bmp': 1.6.1(supports-color@7.2.0) - '@jimp/js-gif': 1.6.1(supports-color@7.2.0) - '@jimp/js-jpeg': 1.6.1(supports-color@7.2.0) - '@jimp/js-png': 1.6.1(supports-color@7.2.0) - '@jimp/js-tiff': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-blit': 1.6.1 - '@jimp/plugin-blur': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-circle': 1.6.1 - '@jimp/plugin-color': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-contain': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-cover': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-crop': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-displace': 1.6.1 - '@jimp/plugin-dither': 1.6.1 - '@jimp/plugin-fisheye': 1.6.1 - '@jimp/plugin-flip': 1.6.1 - '@jimp/plugin-hash': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-mask': 1.6.1 - '@jimp/plugin-print': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-quantize': 1.6.1 - '@jimp/plugin-resize': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-rotate': 1.6.1(supports-color@7.2.0) - '@jimp/plugin-threshold': 1.6.1(supports-color@7.2.0) - '@jimp/types': 1.6.1 - '@jimp/utils': 1.6.1 - transitivePeerDependencies: - - supports-color - jiti@1.21.7: {} - jpeg-js@0.4.4: {} + jiti@2.7.0: {} js-tokens@10.0.0: {} @@ -7995,6 +8258,10 @@ snapshots: js-tokens@9.0.1: {} + js-yaml@4.3.0: + dependencies: + argparse: 2.0.1 + jsdom@29.1.1: dependencies: '@asamuzakjp/css-color': 5.1.11 @@ -8023,8 +8290,14 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} jsonfile@6.2.1: @@ -8042,12 +8315,39 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + knip@5.88.1(@types/node@25.9.5)(typescript@5.9.3): + dependencies: + '@nodelib/fs.walk': 1.2.8 + '@types/node': 25.9.5 + fast-glob: 3.3.3 + formatly: 0.3.0 + jiti: 2.7.0 + minimist: 1.2.8 + oxc-resolver: 11.24.2 + picocolors: 1.1.1 + picomatch: 4.0.4 + smol-toml: 1.7.0 + strip-json-comments: 5.0.3 + typescript: 5.9.3 + unbash: 2.2.0 + yaml: 2.9.0 + zod: 4.4.3 + launder@1.7.1: dependencies: dayjs: 1.11.20 leven@3.1.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lexical@0.45.0: dependencies: '@lexical/internal': 0.45.0 @@ -8064,8 +8364,14 @@ snapshots: lines-and-columns@1.2.4: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lodash.debounce@4.0.8: {} + lodash.merge@4.6.2: {} + lodash.sortby@4.7.0: {} long@4.0.0: {} @@ -8431,8 +8737,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - mime@3.0.0: {} - mimic-response@3.1.0: {} min-indent@1.0.1: {} @@ -8443,6 +8747,10 @@ snapshots: dependencies: brace-expansion: 5.0.6 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.16 + minimatch@5.1.9: dependencies: brace-expansion: 2.1.0 @@ -8473,6 +8781,8 @@ snapshots: napi-build-utils@2.0.0: {} + natural-compare@1.4.0: {} + next@16.2.6(@babel/core@7.29.0(supports-color@7.2.0))(@playwright/test@1.60.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 16.2.6 @@ -8500,7 +8810,7 @@ snapshots: node-abi@3.92.0: dependencies: - semver: 7.8.1 + semver: 7.8.4 node-addon-api@6.1.0: {} @@ -8529,8 +8839,6 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 - omggif@1.0.10: {} - once@1.4.0: dependencies: wrappy: 1.0.2 @@ -8557,24 +8865,58 @@ snapshots: option@0.2.4: {} + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxc-resolver@11.24.2: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.24.2 + '@oxc-resolver/binding-android-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-x64': 11.24.2 + '@oxc-resolver/binding-freebsd-x64': 11.24.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.24.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-musl': 11.24.2 + '@oxc-resolver/binding-openharmony-arm64': 11.24.2 + '@oxc-resolver/binding-wasm32-wasi': 11.24.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} pako@1.0.11: {} - parse-bmfont-ascii@1.0.6: {} - - parse-bmfont-binary@1.0.6: {} - - parse-bmfont-xml@1.1.6: + parent-module@1.0.1: dependencies: - xml-parse-from-string: 1.0.1 - xml2js: 0.5.0 + callsites: 3.1.0 parse-srcset@1.0.2: {} @@ -8582,6 +8924,8 @@ snapshots: dependencies: entities: 8.0.0 + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -8612,10 +8956,6 @@ snapshots: pirates@4.0.7: {} - pixelmatch@5.3.0: - dependencies: - pngjs: 6.0.0 - platform@1.3.6: {} playwright-core@1.60.0: {} @@ -8626,10 +8966,6 @@ snapshots: optionalDependencies: fsevents: 2.3.2 - pngjs@6.0.0: {} - - pngjs@7.0.0: {} - possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.15): @@ -8644,12 +8980,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.5.15 - postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15): + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.15)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: jiti: 1.21.7 postcss: 8.5.15 + yaml: 2.9.0 postcss-nested@6.2.0(postcss@8.5.15): dependencies: @@ -8690,6 +9027,10 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} + + prettier@3.9.5: {} + pretty-bytes@5.6.0: {} pretty-bytes@6.1.1: {} @@ -8889,6 +9230,8 @@ snapshots: require-from-string@2.0.2: {} + resolve-from@4.0.0: {} + resolve@1.22.12: dependencies: es-errors: 1.3.0 @@ -9139,10 +9482,10 @@ snapshots: dependencies: is-arrayish: 0.3.4 - simple-xml-to-json@1.2.7: {} - smob@1.6.2: {} + smol-toml@1.7.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -9253,14 +9596,14 @@ snapshots: strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} + + strip-json-comments@5.0.3: {} + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 - strtok3@10.3.5: - dependencies: - '@tokenizer/token': 0.3.0 - styled-jsx@5.1.6(@babel/core@7.29.0(supports-color@7.2.0))(react@18.3.1): dependencies: client-only: 0.0.1 @@ -9290,7 +9633,7 @@ snapshots: tailwind-merge@2.6.1: {} - tailwindcss@3.4.19: + tailwindcss@3.4.19(yaml@2.9.0): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -9309,7 +9652,7 @@ snapshots: postcss: 8.5.15 postcss-import: 15.1.0(postcss@8.5.15) postcss-js: 4.1.0(postcss@8.5.15) - postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.15)(yaml@2.9.0) postcss-nested: 6.2.0(postcss@8.5.15) postcss-selector-parser: 6.1.2 resolve: 1.22.12 @@ -9401,8 +9744,6 @@ snapshots: tinybench@2.9.0: {} - tinycolor2@1.6.0: {} - tinyexec@0.3.2: {} tinyglobby@0.2.16: @@ -9428,12 +9769,6 @@ snapshots: dependencies: is-number: 7.0.0 - token-types@6.1.2: - dependencies: - '@borewit/text-codec': 0.2.2 - '@tokenizer/token': 0.3.0 - ieee754: 1.2.1 - tough-cookie@6.0.1: dependencies: tldts: 7.0.30 @@ -9448,6 +9783,10 @@ snapshots: trough@2.2.0: {} + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-interface-checker@0.1.13: {} tslib@2.8.1: {} @@ -9456,6 +9795,10 @@ snapshots: dependencies: safe-buffer: 5.1.2 + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@0.16.0: {} typed-array-buffer@1.0.3: @@ -9491,9 +9834,20 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typescript-eslint@8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3))(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.65.0(supports-color@7.2.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.65.0(eslint@9.39.5(jiti@1.21.7)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3) + eslint: 9.39.5(jiti@1.21.7)(supports-color@7.2.0) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + typescript@5.9.3: {} - uint8array-extras@1.5.0: {} + unbash@2.2.0: {} unbox-primitive@1.1.0: dependencies: @@ -9562,6 +9916,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + use-callback-ref@1.3.3(@types/react@18.3.29)(react@18.3.1): dependencies: react: 18.3.1 @@ -9577,10 +9935,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.29 - utif2@4.1.0: - dependencies: - pako: 1.0.11 - util-deprecate@1.0.2: {} vfile-message@4.0.3: @@ -9593,13 +9947,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@25.9.5)(jiti@1.21.7)(supports-color@7.2.0)(terser@5.48.0): + vite-node@3.2.4(@types/node@25.9.5)(jiti@1.21.7)(supports-color@7.2.0)(terser@5.48.0)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@7.2.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0) + vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -9614,18 +9968,18 @@ snapshots: - tsx - yaml - vite-plugin-pwa@0.21.2(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0))(workbox-build@7.4.1(@types/babel__core@7.20.5)(supports-color@7.2.0))(workbox-window@7.4.1): + vite-plugin-pwa@0.21.2(supports-color@7.2.0)(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0))(workbox-build@7.4.1(@types/babel__core@7.20.5)(supports-color@7.2.0))(workbox-window@7.4.1): dependencies: debug: 4.4.3(supports-color@7.2.0) pretty-bytes: 6.1.1 tinyglobby: 0.2.16 - vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0) + vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) workbox-build: 7.4.1(@types/babel__core@7.20.5)(supports-color@7.2.0) workbox-window: 7.4.1 transitivePeerDependencies: - supports-color - vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0): + vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.4) @@ -9638,12 +9992,13 @@ snapshots: fsevents: 2.3.3 jiti: 1.21.7 terser: 5.48.0 + yaml: 2.9.0 - vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0): + vitest@3.2.4(@types/debug@4.1.13)(@types/node@25.9.5)(jiti@1.21.7)(jsdom@29.1.1)(supports-color@7.2.0)(terser@5.48.0)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)) + '@vitest/mocker': 3.2.4(vite@6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -9661,8 +10016,8 @@ snapshots: tinyglobby: 0.2.16 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0) - vite-node: 3.2.4(@types/node@25.9.5)(jiti@1.21.7)(supports-color@7.2.0)(terser@5.48.0) + vite: 6.4.2(@types/node@25.9.5)(jiti@1.21.7)(terser@5.48.0)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@25.9.5)(jiti@1.21.7)(supports-color@7.2.0)(terser@5.48.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.13 @@ -9686,6 +10041,8 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + walk-up-path@4.0.0: {} + webidl-conversions@4.0.2: {} webidl-conversions@8.0.1: {} @@ -9756,6 +10113,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + workbox-background-sync@7.4.1: dependencies: idb: 7.1.1 @@ -9889,19 +10248,10 @@ snapshots: xml-name-validator@5.0.0: {} - xml-parse-from-string@1.0.1: {} - - xml2js@0.5.0: - dependencies: - sax: 1.6.0 - xmlbuilder: 11.0.1 - xml@1.0.1: {} xmlbuilder@10.1.1: {} - xmlbuilder@11.0.1: {} - xmlchars@2.2.0: {} y-indexeddb@9.0.12(yjs@13.6.31): @@ -9911,11 +10261,15 @@ snapshots: yallist@3.1.1: {} + yaml@2.9.0: {} + yjs@13.6.31: dependencies: lib0: 0.2.117 - zod@3.25.76: {} + yocto-queue@0.1.0: {} + + zod@4.4.3: {} zustand@5.0.13(@types/react@18.3.29)(react@18.3.1): optionalDependencies: diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx deleted file mode 100644 index 6e96a59..0000000 --- a/src/components/ui/tooltip.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client' - -import * as React from 'react' -import * as TooltipPrimitive from '@radix-ui/react-tooltip' -import { cn } from '@/lib/utils' - -const TooltipProvider = TooltipPrimitive.Provider - -const Tooltip = TooltipPrimitive.Root - -const TooltipTrigger = TooltipPrimitive.Trigger - -const TooltipContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, sideOffset = 4, ...props }, ref) => ( - - - -)) -TooltipContent.displayName = TooltipPrimitive.Content.displayName - -export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/src/core/glossary/tbx.ts b/src/core/glossary/tbx.ts index f498921..65e7e62 100644 --- a/src/core/glossary/tbx.ts +++ b/src/core/glossary/tbx.ts @@ -17,6 +17,8 @@ const XML_ESCAPE: Record = { } function escapeXml(s: string): string { + // Strip XML 1.0 invalid control chars — matching them is intentional. + // eslint-disable-next-line no-control-regex const cleaned = s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '') return cleaned.replace(/[&<>"']/g, (c) => XML_ESCAPE[c]) } diff --git a/src/core/xml/escape.ts b/src/core/xml/escape.ts index ffeddf4..bf12ec4 100644 --- a/src/core/xml/escape.ts +++ b/src/core/xml/escape.ts @@ -7,7 +7,9 @@ const XML_ESCAPE: Record = { } export function escapeXml(s: string): string { - // Strip XML 1.0 invalid control chars (except tab, LF, CR). + // Strip XML 1.0 invalid control chars (except tab, LF, CR) — matching them is + // the whole point here. + // eslint-disable-next-line no-control-regex const cleaned = s.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '') return cleaned.replace(/[&<>"']/g, (c) => XML_ESCAPE[c]) } diff --git a/src/features/dictionary/index.tsx b/src/features/dictionary/index.tsx deleted file mode 100644 index 9e26da0..0000000 --- a/src/features/dictionary/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default function DictionaryPage() { - return ( -
-

- Dictionary -

-
- ) -} diff --git a/src/features/editor/rich/RichSegmentEditor.tsx b/src/features/editor/rich/RichSegmentEditor.tsx index ae5bf0f..7a47162 100644 --- a/src/features/editor/rich/RichSegmentEditor.tsx +++ b/src/features/editor/rich/RichSegmentEditor.tsx @@ -392,7 +392,7 @@ function EditorLogic(props: RichSegmentEditorProps) { unregisters.forEach((u) => u()) propsRef.current.registerHandle(null) } - // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editor]) // When the DB target diverges from what we last saved, an external action diff --git a/src/features/editor/rich/TrackedChangesPlugin.tsx b/src/features/editor/rich/TrackedChangesPlugin.tsx index 1f8fac8..5b0af98 100644 --- a/src/features/editor/rich/TrackedChangesPlugin.tsx +++ b/src/features/editor/rich/TrackedChangesPlugin.tsx @@ -98,7 +98,7 @@ export function TrackedChangesPlugin({ locked }: { locked?: boolean }) { ), ] return () => unregister.forEach((u) => u()) - // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editor]) return null diff --git a/src/storage/schemas.ts b/src/storage/schemas.ts deleted file mode 100644 index b29432d..0000000 --- a/src/storage/schemas.ts +++ /dev/null @@ -1 +0,0 @@ -export type { Project, Segment, TMEntry, GlossaryEntry } from '@/core/types' diff --git a/src/storage/sync/syncSession.ts b/src/storage/sync/syncSession.ts index 0473160..8c68ab2 100644 --- a/src/storage/sync/syncSession.ts +++ b/src/storage/sync/syncSession.ts @@ -61,7 +61,7 @@ export function startSyncSession(opts: SyncSessionOptions): SyncSessionHandle { const presence = new PresenceTracker(peerId) const subscribers = new Set<(peers: PeerPresence[]) => void>() - let displayName = opts.displayName + const displayName = opts.displayName let activeSegmentId: string | undefined let caret: CaretRange | undefined let heartbeat: ReturnType | null = null From 91c11503a665307c4ce2f5bb5b6a15ef2e16fa75 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 22 Jul 2026 01:05:48 +0000 Subject: [PATCH 6/8] =?UTF-8?q?Phase=20E2=20=E2=80=94=20style:=20prettier?= =?UTF-8?q?=20--write=20across=20the=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure formatting, no logic changes — typecheck, eslint, 728 unit tests and the build are all green before and after. Isolated into its own commit so it can be added to .git-blame-ignore-revs and skipped in review. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_013kzE8hPSJxu19tzs7kuMMa --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +- .vscode/settings.json | 6 +- LICENSE.md | 8 +- docs/architecture.md | 83 ++--- docs/cloud.md | 72 ++--- docs/refactor-audit.md | 63 ++-- docs/revamp/ROADMAP.md | 57 ++-- docs/revamp/STATUS.md | 73 ++--- docs/revamp/prompts/phase-0-1.md | 4 + docs/revamp/prompts/phase-1-1.md | 4 + docs/revamp/prompts/phase-1-2.md | 4 + docs/revamp/prompts/phase-1-3.md | 4 + docs/revamp/prompts/phase-1-4.md | 4 + docs/revamp/prompts/phase-1-5.md | 4 + docs/revamp/prompts/phase-1-6.md | 4 + docs/revamp/prompts/phase-2-1.md | 4 + docs/revamp/prompts/phase-2-2.md | 4 + docs/revamp/prompts/phase-2-3.md | 4 + docs/revamp/prompts/phase-2-4.md | 4 + docs/revamp/prompts/phase-3-1.md | 4 + docs/revamp/prompts/phase-3-2.md | 4 + docs/revamp/prompts/phase-3-3.md | 4 + docs/revamp/prompts/phase-3-4.md | 4 + docs/revamp/prompts/phase-4-1.md | 4 + docs/revamp/prompts/phase-4-2.md | 4 + docs/revamp/prompts/phase-4-3.md | 4 + docs/revamp/prompts/phase-4-4.md | 4 + docs/revamp/prompts/phase-5-1.md | 4 + docs/revamp/prompts/phase-5-2.md | 4 + docs/revamp/prompts/phase-5-3.md | 4 + docs/revamp/prompts/phase-6-1.md | 4 + docs/revamp/prompts/phase-6-2.md | 4 + docs/revamp/prompts/phase-6-3.md | 4 + docs/revamp/prompts/phase-6-4.md | 4 + docs/roadmap-professional-features.md | 165 +++++----- docs/vision/architecture-vision.md | 63 +++- docs/vision/overview.md | 26 ++ docs/vision/product-features.md | 99 ++++++ docs/vision/product-vision.md | 66 ++++ docs/vision/ux-vision.md | 76 +++++ public/guide/standards.md | 54 ++-- public/guide/theory-glossary.md | 8 +- public/guide/workflow.md | 33 +- public/manifest.webmanifest | 14 +- scripts/build-corpora.mjs | 12 +- src-tauri/README.md | 16 +- src-tauri/tauri.conf.json | 5 +- src/app/ErrorBoundary.tsx | 4 +- src/app/providers.tsx | 8 +- src/components/brand/VerbalisMark.tsx | 9 +- src/components/layout/BrandFooter.tsx | 5 +- src/components/layout/PageHeader.tsx | 6 +- src/components/layout/Sidebar.tsx | 2 +- src/components/layout/TopBar.tsx | 7 +- src/components/ui/badge.tsx | 9 +- src/components/ui/button.tsx | 18 +- src/components/ui/command.tsx | 4 +- src/components/ui/input.tsx | 36 +-- src/components/ui/select.tsx | 40 +-- src/core/bilingual/inlineTags.ts | 4 +- src/core/bilingual/xliff12.ts | 8 +- src/core/corpus/manifest.ts | 5 +- src/core/documents/docxImport.ts | 38 ++- src/core/documents/fromSegments.ts | 4 +- src/core/documents/render.ts | 4 +- src/core/documents/targetRuns.ts | 6 +- src/core/documents/toDocx.ts | 17 +- src/core/embeddings/index.ts | 6 +- src/core/extensions/types.ts | 7 +- src/core/glossary/match.ts | 5 +- src/core/glossary/tbx.ts | 10 +- src/core/glossary/tsv.ts | 4 +- src/core/glossary/wiktionary.ts | 5 +- src/core/mt/claude.ts | 7 +- src/core/mt/mymemory.ts | 40 ++- src/core/mt/types.ts | 5 +- src/core/qa/checks.ts | 13 +- src/core/qa/registryRules.ts | 4 +- src/core/segmentation/sbdOptions.ts | 176 ++++++++++- src/core/segmentation/txt.ts | 5 +- src/core/spell/loader.ts | 5 +- src/core/spell/offsets.ts | 6 +- src/core/text/autocorrect.ts | 3 +- src/core/tm/semantic.ts | 6 +- src/core/websearch/providers.ts | 42 ++- src/core/workflow/rules.ts | 6 +- .../connectors/ConnectorFilePicker.tsx | 12 +- src/extensions/connectors/gdrive/config.ts | 4 +- src/extensions/connectors/gdrive/driveApi.ts | 20 +- src/extensions/connectors/gdrive/gis.ts | 46 ++- src/extensions/connectors/gdrive/index.ts | 14 +- src/extensions/connectors/onedrive/config.ts | 5 +- .../connectors/onedrive/graphApi.ts | 18 +- src/extensions/connectors/onedrive/index.ts | 7 +- src/extensions/connectors/onedrive/msal.ts | 3 +- src/extensions/mt/index.ts | 10 +- src/features/about/index.tsx | 63 ++-- src/features/account/AccountMenu.tsx | 5 +- .../account/AccountSettingsSection.tsx | 13 +- src/features/account/SignInDialog.tsx | 4 +- src/features/account/useAuthStore.ts | 7 +- src/features/addons/AddonsPage.tsx | 31 +- .../command-palette/CommandPalette.tsx | 53 +--- src/features/corpora/CorpusImportButton.tsx | 18 +- src/features/corpora/CorpusPackCard.tsx | 7 +- src/features/editor/EditModeToggle.tsx | 6 +- src/features/editor/EditorPage.tsx | 14 +- src/features/editor/EditorTips.tsx | 35 ++- src/features/editor/MobileSidebarSheet.tsx | 10 +- src/features/editor/SegmentRow.tsx | 20 +- .../editor/analysis/AnalysisDialog.tsx | 11 +- src/features/editor/changes/ChangesPanel.tsx | 17 +- src/features/editor/changes/resolveOps.ts | 16 +- .../editor/comments/CommentsPanel.tsx | 6 +- .../editor/comments/SegmentComments.tsx | 17 +- src/features/editor/comments/commentOps.ts | 12 +- .../editor/concordance/ConcordanceDialog.tsx | 14 +- .../editor/findReplace/FindReplaceDialog.tsx | 16 +- .../editor/glossary/GlossaryPanel.tsx | 21 +- .../editor/history/SegmentHistoryDialog.tsx | 5 +- src/features/editor/layoutSync.ts | 6 +- src/features/editor/lookup/LookupPanel.tsx | 7 +- src/features/editor/mt/MTPanel.tsx | 5 +- src/features/editor/mt/useMTSettings.ts | 11 +- src/features/editor/mt/useMTTranslate.ts | 3 +- .../editor/peers/PeersPresenceChip.tsx | 4 +- src/features/editor/peers/useProjectSync.ts | 5 +- .../editor/preview/DocumentPreview.tsx | 28 +- src/features/editor/rich/ChangeHoverCard.tsx | 5 +- src/features/editor/rich/ChangeMarkNode.ts | 4 +- src/features/editor/rich/CommentMarkNode.ts | 4 +- src/features/editor/rich/FormatToolbar.tsx | 4 +- src/features/editor/rich/InlineTagNode.tsx | 5 +- .../editor/rich/RemoteCaretOverlay.tsx | 10 +- .../editor/rich/RichSegmentEditor.tsx | 13 +- .../editor/rich/SpellUnderlinePlugin.tsx | 9 +- .../editor/rich/TrackedChangesPlugin.tsx | 1 - src/features/editor/rich/comments.ts | 6 +- src/features/editor/rich/resolve.ts | 7 +- src/features/editor/rich/richOffsets.ts | 3 +- src/features/editor/rich/suggest.ts | 4 +- src/features/editor/segmentAutosave.ts | 5 +- .../editor/segments/SegmentActionsMenu.tsx | 4 +- .../editor/segments/SplitSegmentDialog.tsx | 4 +- src/features/editor/spell/SpellPanel.tsx | 5 +- src/features/editor/tm/TMPanel.tsx | 5 +- .../editor/tools/ExportDocxButton.tsx | 25 +- src/features/editor/useEditorModeStore.ts | 3 +- src/features/editor/useSidebarPanelStore.ts | 5 +- .../editor/workflow/useWorkflowStore.ts | 4 +- src/features/guide/Markdown.tsx | 5 +- src/features/guide/index.tsx | 5 +- src/features/import/ImportDialog.tsx | 288 +++++++++--------- src/features/lookup/useQuickLookupStore.ts | 1 - src/features/projects/DeleteProjectDialog.tsx | 10 +- .../projects/DuplicateProjectDialog.tsx | 6 +- src/features/projects/EditProjectDialog.tsx | 9 +- src/features/projects/ProjectCard.tsx | 13 +- src/features/projects/cloud/CloudControls.tsx | 7 +- .../projects/cloud/ManageMembersDialog.tsx | 38 ++- .../projects/cloud/OpenCloudProjectDialog.tsx | 22 +- src/features/projects/index.tsx | 15 +- .../settings/EditorSettingsSection.tsx | 6 +- .../settings/LookupSettingsSection.tsx | 4 +- src/features/settings/MTSettingsSection.tsx | 56 ++-- .../settings/ProfileSettingsSection.tsx | 8 +- src/features/settings/SemanticTMSection.tsx | 10 +- .../settings/SpellSettingsSection.tsx | 4 +- .../settings/WebSearchSettingsSection.tsx | 13 +- src/features/settings/index.tsx | 15 +- src/features/shortcuts/ShortcutsDialog.tsx | 9 +- .../terminology/GlossaryEditDialog.tsx | 14 +- src/features/terminology/GlossaryTable.tsx | 15 +- src/features/terminology/WiktionaryLookup.tsx | 19 +- src/features/terminology/index.tsx | 10 +- src/features/tm/TMEditDialog.tsx | 5 +- src/features/tm/TMImportButton.tsx | 12 +- src/features/tm/TMTable.tsx | 2 +- src/features/translate/TranslateWorkspace.tsx | 79 ++--- src/features/translate/index.tsx | 6 +- src/storage/cloud/members.ts | 7 +- src/storage/cloud/projectCloud.ts | 7 +- src/storage/cloud/rowSync.ts | 5 +- src/storage/cloud/settingsSync.ts | 8 +- src/storage/cloud/ydocPersistence.ts | 10 +- src/storage/repositories/corpusRepo.ts | 3 +- src/storage/repositories/documentRepo.ts | 9 +- src/storage/repositories/glossaryRepo.ts | 5 +- src/storage/repositories/projectRepo.ts | 3 +- src/storage/repositories/segmentRepo.ts | 7 +- src/storage/repositories/settingsRepo.ts | 14 +- src/storage/repositories/versionRepo.ts | 5 +- src/storage/sync/crypto.ts | 5 +- src/storage/sync/syncManager.ts | 3 +- src/storage/sync/transport/chunking.ts | 5 +- src/storage/sync/transport/index.ts | 4 +- .../sync/transport/supabaseRealtime.ts | 4 +- src/storage/sync/transport/tauri.ts | 5 +- src/styles/globals.css | 3 +- tailwind.config.ts | 20 +- tests/e2e/comments-anchored.spec.ts | 4 +- tests/e2e/fixtures/sample.md | 4 +- tests/e2e/mt-flow.spec.ts | 12 +- tests/e2e/tm-flow.spec.ts | 4 +- tests/e2e/tracked-changes.spec.ts | 4 +- tests/unit/AddonsPage.test.tsx | 4 +- tests/unit/ConnectorFilePicker.test.tsx | 16 +- tests/unit/MTPanel.test.tsx | 7 +- tests/unit/OfflineBadge.test.tsx | 5 +- tests/unit/TMEditDialog.test.tsx | 10 +- tests/unit/analysis.report.test.ts | 12 +- tests/unit/bilingual.xliff12.test.ts | 5 +- tests/unit/changes.extract.test.ts | 7 +- tests/unit/changes.resolve.test.ts | 8 +- tests/unit/cloud.members.test.ts | 39 ++- tests/unit/cloud.projectCloud.test.ts | 6 +- tests/unit/cloud.rowSync.repo.test.ts | 7 +- tests/unit/cloud.rowSync.test.ts | 32 +- tests/unit/cloud.settingsSync.test.ts | 9 +- tests/unit/cloud.supabaseClient.test.ts | 6 +- tests/unit/connectors.driveApi.test.ts | 36 ++- tests/unit/connectors.gdrive.test.ts | 20 +- tests/unit/connectors.graphApi.test.ts | 34 ++- tests/unit/connectors.onedrive.test.ts | 24 +- tests/unit/corpus.customPack.test.ts | 13 +- tests/unit/corpus.match.test.ts | 6 +- tests/unit/corpusRepo.test.ts | 7 +- tests/unit/crdtBridge.test.ts | 4 +- tests/unit/db.migration.test.ts | 56 +++- tests/unit/documentRepo.test.ts | 20 +- tests/unit/documents.docxImport.test.ts | 19 +- tests/unit/documents.fromSegments.test.ts | 8 +- tests/unit/documents.render.test.ts | 70 ++++- tests/unit/documents.targetRuns.test.ts | 12 +- tests/unit/documents.toDocx.test.ts | 49 ++- tests/unit/embeddingsRepo.test.ts | 5 +- tests/unit/glossary.csv.multiterm.test.ts | 4 +- tests/unit/glossary.csv.test.ts | 10 +- tests/unit/glossary.match.test.ts | 18 +- tests/unit/glossary.tsv.test.ts | 4 +- tests/unit/glossaryRepo.test.ts | 6 +- tests/unit/history.diff.test.ts | 14 +- tests/unit/i18n.languages.test.ts | 6 +- tests/unit/mt.ollama.test.ts | 13 +- tests/unit/numbers.test.ts | 4 +- tests/unit/projectRepo.test.ts | 43 ++- tests/unit/qa.checks.test.ts | 12 +- tests/unit/qa.registryRules.test.ts | 5 +- tests/unit/richText.test.ts | 14 +- tests/unit/segmentCrdt.test.ts | 14 +- tests/unit/segmentRepo.test.ts | 28 +- tests/unit/segmentation.docx.test.ts | 9 +- tests/unit/segmentation.test.ts | 6 +- tests/unit/segments.test.ts | 16 +- tests/unit/settings.about.test.tsx | 4 +- tests/unit/sync.chunking.test.ts | 10 +- tests/unit/sync.presence.test.ts | 8 +- tests/unit/sync.reverseBridge.test.ts | 20 +- tests/unit/tm.concordance.test.ts | 9 +- tests/unit/tm.match.test.ts | 4 +- tests/unit/tm.semantic.test.ts | 26 +- tests/unit/tmRepo.test.ts | 6 +- tests/unit/useNetworkStatus.test.ts | 5 +- tests/unit/versionRepo.changes.test.ts | 6 +- tests/unit/versionRepo.test.ts | 18 +- tests/unit/workflow.rules.test.ts | 24 +- tsconfig.json | 5 +- vite.config.ts | 14 +- 268 files changed, 2708 insertions(+), 1561 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d139126..c4dc6c4 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,15 +9,13 @@ assignees: '' **What happened** A clear description of the bug. -**Steps to reproduce** -1. -2. -3. +**Steps to reproduce** 1. 2. 3. **Expected behaviour** What you expected to happen instead. **Environment** + - Build SHA (from the About page): - Browser / OS: - Mode: local-only / signed-in diff --git a/.vscode/settings.json b/.vscode/settings.json index 03adc8d..c5ee1a9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "IDX.aI.enableInlineCompletion": true, - "IDX.aI.enableCodebaseIndexing": true -} \ No newline at end of file + "IDX.aI.enableInlineCompletion": true, + "IDX.aI.enableCodebaseIndexing": true +} diff --git a/LICENSE.md b/LICENSE.md index 526a6c9..d0ea845 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -33,8 +33,8 @@ licence to use, copy, modify, and self-host the Software for their own work, > existing "Created by Pedro Brito" credit and the link to > intact in the application's > About/credits screen, and — where you publish work, a fork, or a derivative -> built on the Software — state that it is *"powered by Verbalis, created by -> Pedro Brito"* with a link back to this repository. +> built on the Software — state that it is _"powered by Verbalis, created by +> Pedro Brito"_ with a link back to this repository. That attribution is the only thing asked of Individuals. @@ -78,6 +78,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- -*Not sure which tier applies to you? If you are one person using Verbalis for +_Not sure which tier applies to you? If you are one person using Verbalis for your own translation work, you are covered for free under Section 2 — just keep -the credit. Anything bigger, reach out and we'll sort out fair terms.* +the credit. Anything bigger, reach out and we'll sort out fair terms._ diff --git a/docs/architecture.md b/docs/architecture.md index d588438..540c59d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,20 +6,20 @@ VERBALIS is a local-first, browser-native CAT (Computer-Assisted Translation) to ## Tech Stack -| Layer | Choice | Notes | -|---|---|---| -| Framework | React 18 + TypeScript | Strict mode, full type safety | -| Bundler | Vite 6 | base: '/' for the custom-domain root (override with `BASE_PATH` for subdirectory builds) | -| Styling | Tailwind CSS 3 + shadcn/ui | Owned components, Radix primitives | -| State | Zustand (UI) + TanStack Query v5 (async) | No Redux boilerplate | -| Routing | React Router v6 HashRouter | GH Pages SPA compatibility | -| Storage | Dexie.js v4 (IndexedDB) | Promise API, live hooks, migrations | -| Parsing | unified + remark-parse + remark-gfm + sbd | AST-based, not regex | -| DOCX | mammoth | Only viable browser-side parser | -| Fuzzy search | Fuse.js (TM) + MiniSearch (terminology) | Different tools for different jobs | -| Workers | Comlink | Makes Web Worker calls look like async functions | -| PWA | vite-plugin-pwa (Workbox) | Offline support, installable | -| Fonts | geist npm package | Self-hosted, works fully offline | +| Layer | Choice | Notes | +| ------------ | ----------------------------------------- | ---------------------------------------------------------------------------------------- | +| Framework | React 18 + TypeScript | Strict mode, full type safety | +| Bundler | Vite 6 | base: '/' for the custom-domain root (override with `BASE_PATH` for subdirectory builds) | +| Styling | Tailwind CSS 3 + shadcn/ui | Owned components, Radix primitives | +| State | Zustand (UI) + TanStack Query v5 (async) | No Redux boilerplate | +| Routing | React Router v6 HashRouter | GH Pages SPA compatibility | +| Storage | Dexie.js v4 (IndexedDB) | Promise API, live hooks, migrations | +| Parsing | unified + remark-parse + remark-gfm + sbd | AST-based, not regex | +| DOCX | mammoth | Only viable browser-side parser | +| Fuzzy search | Fuse.js (TM) + MiniSearch (terminology) | Different tools for different jobs | +| Workers | Comlink | Makes Web Worker calls look like async functions | +| PWA | vite-plugin-pwa (Workbox) | Offline support, installable | +| Fonts | geist npm package | Self-hosted, works fully offline | ## Key Folder Map @@ -41,22 +41,22 @@ src/ ## Storage Schema (v7) -| Table | Indexes | Since | -|---|---|---| -| projects | id, name, updatedAt | v1 | -| segments | id, projectId, index, status, [projectId+status], [projectId+index] | v1 (compound idx v4) | -| tm | id, source, sourceLang, targetLang, projectId, corpusId, updatedAt | v1 | -| glossary | id, term, projectId, updatedAt | v1 | -| settings | &key | v2 | -| embeddings | id, tmId, model, [tmId+model] | v2 | -| corpusTerms | id, corpusId | v3 | -| corpusPacks | id | v3 | -| projectTemplates | projectId | v4 | -| versions | id, projectId, createdAt, [projectId+createdAt] | v5 | -| documents | id, projectId | v6 | -| blocks | id, documentId, projectId, [documentId+index] | v6 | -| assets | id, documentId, projectId | v6 | -| syncTombstones | [resource+rowId], resource, deletedAt | v7 | +| Table | Indexes | Since | +| ---------------- | ------------------------------------------------------------------- | -------------------- | +| projects | id, name, updatedAt | v1 | +| segments | id, projectId, index, status, [projectId+status], [projectId+index] | v1 (compound idx v4) | +| tm | id, source, sourceLang, targetLang, projectId, corpusId, updatedAt | v1 | +| glossary | id, term, projectId, updatedAt | v1 | +| settings | &key | v2 | +| embeddings | id, tmId, model, [tmId+model] | v2 | +| corpusTerms | id, corpusId | v3 | +| corpusPacks | id | v3 | +| projectTemplates | projectId | v4 | +| versions | id, projectId, createdAt, [projectId+createdAt] | v5 | +| documents | id, projectId | v6 | +| blocks | id, documentId, projectId, [documentId+index] | v6 | +| assets | id, documentId, projectId | v6 | +| syncTombstones | [resource+rowId], resource, deletedAt | v7 | Migrations are handled by Dexie's versioning system (`this.version(N).stores(...)`). Always increment, never modify existing version blocks. Notable steps: v3 adds the bundled-corpora tables + a `corpusId` index on `tm`; v4 adds compound segment indexes and moves the XLIFF template blob to its own table; v5 adds version snapshots; v6 adds the document/block model (backfilled from `sourceMeta.blockIndex`); v7 adds `updatedAt` indexes on `tm`/`glossary` and the `syncTombstones` table for the personal-resource cloud reconciler. @@ -67,6 +67,7 @@ Migrations are handled by Dexie's versioning system (`this.version(N).stores(... Static build → GitHub Actions → GitHub Pages at `https://verbalis.britx.me/`. Critical GH Pages constraints: + - `base: '/'` in vite.config.ts, overridable via the `BASE_PATH` env var (e.g. `BASE_PATH=/verbalis/ pnpm build`) for anyone forking this project into a path-based GitHub Pages deployment instead @@ -82,17 +83,17 @@ DOCX import uses `mammoth.convertToHtml`, then a small DOM walker (`src/core/seg ## Phase Roadmap -| Phase | Scope | -|---|---| -| 0 | Foundation — scaffold, CI/CD, PWA, app shell ✅ | -| 1 | TXT + MD import, segmentation, side-by-side editor ✅ | -| 2 | Translation Memory — store, exact/fuzzy match, TMX import/export ✅ | -| 3 | Terminology — glossary CRUD, CSV + TBX I/O, inline editor panel, Wiktionary adapter ✅ | -| 4 | DOCX import, command palette, review modes ✅ | -| 5 | PWA hardening, offline edge cases, update notification ✅ | -| 6 | AI integrations (Ollama, Claude, LibreTranslate), semantic TM ✅ | -| 7+ | Project-level exports, terminology extraction, collaborative TM | -| 8+ | Professional CAT features — rich editor, segment handling, versioning, LAN collaboration, document standards. See [`roadmap-professional-features.md`](roadmap-professional-features.md) | +| Phase | Scope | +| ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | Foundation — scaffold, CI/CD, PWA, app shell ✅ | +| 1 | TXT + MD import, segmentation, side-by-side editor ✅ | +| 2 | Translation Memory — store, exact/fuzzy match, TMX import/export ✅ | +| 3 | Terminology — glossary CRUD, CSV + TBX I/O, inline editor panel, Wiktionary adapter ✅ | +| 4 | DOCX import, command palette, review modes ✅ | +| 5 | PWA hardening, offline edge cases, update notification ✅ | +| 6 | AI integrations (Ollama, Claude, LibreTranslate), semantic TM ✅ | +| 7+ | Project-level exports, terminology extraction, collaborative TM | +| 8+ | Professional CAT features — rich editor, segment handling, versioning, LAN collaboration, document standards. See [`roadmap-professional-features.md`](roadmap-professional-features.md) | ## Phase 5 — PWA Layer diff --git a/docs/cloud.md b/docs/cloud.md index 3301120..09da39c 100644 --- a/docs/cloud.md +++ b/docs/cloud.md @@ -15,10 +15,10 @@ never talks to a real Supabase instance. The live Verbalis project is already provisioned: -| | | -| --- | --- | -| Project ref | `qutcuzlppbjbsymowavc` (region `us-east-2`, Postgres 17) | -| `VITE_SUPABASE_URL` | `https://qutcuzlppbjbsymowavc.supabase.co` | +| | | +| ------------------------ | ----------------------------------------------------------------------------------- | +| Project ref | `qutcuzlppbjbsymowavc` (region `us-east-2`, Postgres 17) | +| `VITE_SUPABASE_URL` | `https://qutcuzlppbjbsymowavc.supabase.co` | | `VITE_SUPABASE_ANON_KEY` | `sb_publishable_c8YdrT2-abKdW1Kl7FDKyA_v5OwqLdY` (publishable — safe in the bundle) | These two values are also in `.env.example` — copy it to `.env.local` for local @@ -35,8 +35,8 @@ production, the repository Variables in §1. Set these at build time (e.g. in the CI/deploy environment). Leaving `VITE_SUPABASE_URL` or `VITE_SUPABASE_ANON_KEY` unset keeps the app local-only. -**Production (GitHub Pages / `deploy.yml`)**: add them under *Settings → Secrets -and variables → Actions*. A repository **Variable** is the natural home (all +**Production (GitHub Pages / `deploy.yml`)**: add them under _Settings → Secrets +and variables → Actions_. A repository **Variable** is the natural home (all three are public — they end up in the client bundle), but `deploy.yml` accepts either a Variable or a Secret via a fallback chain, and also accepts the URL under the unprefixed name `SUPABASE_URL`: @@ -51,11 +51,11 @@ build; the `sb_secret_...` key must never reach the client bundle. **Local dev**: `cp .env.example .env.local` (git-ignored) and run `pnpm dev`. -| Variable | Required | Purpose | -| --- | --- | --- | -| `VITE_SUPABASE_URL` | yes (to enable cloud) | Supabase project URL, e.g. `https://xxxx.supabase.co` | -| `VITE_SUPABASE_ANON_KEY` | yes (to enable cloud) | The project's anon / publishable key (safe to ship in a client bundle) | -| `VITE_AUTH_PROVIDERS` | no | Comma-separated OAuth providers to show, using **Supabase provider ids**: `google,azure,apple`. Defaults to `google`. Providers not configured server-side are simply hidden. | +| Variable | Required | Purpose | +| ------------------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `VITE_SUPABASE_URL` | yes (to enable cloud) | Supabase project URL, e.g. `https://xxxx.supabase.co` | +| `VITE_SUPABASE_ANON_KEY` | yes (to enable cloud) | The project's anon / publishable key (safe to ship in a client bundle) | +| `VITE_AUTH_PROVIDERS` | no | Comma-separated OAuth providers to show, using **Supabase provider ids**: `google,azure,apple`. Defaults to `google`. Providers not configured server-side are simply hidden. | > Note on provider ids: Microsoft is `azure` (not `microsoft`) in Supabase Auth. @@ -92,7 +92,7 @@ and safe to re-run: allowlist lives in `src/storage/cloud/settingsSync.ts`). - `0004_personal_resources.sql` — the user's personal term bank + TM (`personal_glossary` / `personal_tm`, each `id, user_id, updated_at, deleted, - payload jsonb`) + owner-scoped RLS. A generic cursor-based reconciler +payload jsonb`) + owner-scoped RLS. A generic cursor-based reconciler (`src/storage/cloud/rowSync.ts`) syncs them with per-row LWW and soft-delete tombstones. Backs Phase 3.4; **bundled corpora never sync** (corpus-seeded TM carries a `corpusId` and is filtered out). @@ -105,32 +105,32 @@ and safe to re-run: `has_project_role` RLS helpers into a non-API `private` schema so they are not reachable as PostgREST RPCs (clears the database linter). Apply after `0005`. - `0007_compaction.sql` — the `claim_compaction(project_id, expected_seq, state, - up_to_id)` RPC (SECURITY DEFINER, membership-checked): optimistically installs +up_to_id)` RPC (SECURITY DEFINER, membership-checked): optimistically installs a fresh compacted `ydoc_state` snapshot (guarded by the `seq` generation) and prunes the subsumed `ydoc_updates` rows, so the append log stays bounded. Backs - Phase 4.3. Apply after `0006`. *(Numbering note: ROADMAP §4.3 sketched + Phase 4.3. Apply after `0006`. _(Numbering note: ROADMAP §4.3 sketched `0005_compaction.sql`, but `0005`/`0006` were taken by the 4.1 project migrations, so it ships as `0007`; and the RPC signature is widened from the sketch's `(project_id, expected_seq)` to also carry the new snapshot + the pruned-through id, so the whole compaction is one atomic, RLS-safe - transaction.)* + transaction.)_ - `0008_member_policies.sql` — member management: adds `profiles.email` (backfilled - + seeded on signup) and a co-member `profiles` SELECT policy (`private.shares_project`); - switches `project_members` update/delete to **project_manager-only** (insert still - allows the owner's publish self-seed); a `enforce_min_one_pm` trigger that blocks - removing or demoting a project's **last** manager; and the `invite_member(project_id, - email, role)` RPC (SECURITY DEFINER, PM-checked) that resolves the email to a user id - and upserts the membership (returns NULL when no account has that email — no - enumeration surface). Backs Phase 5.1. Apply after `0007`. *(Numbering note: ROADMAP - §5.1 sketched `0006_member_policies.sql`, but `0006`/`0007` were taken, so it ships as - `0008`; invite resolves the email inside the RPC rather than via a client-side profiles - lookup, keeping emails/ids server-side.)* + - seeded on signup) and a co-member `profiles` SELECT policy (`private.shares_project`); + switches `project_members` update/delete to **project_manager-only** (insert still + allows the owner's publish self-seed); a `enforce_min_one_pm` trigger that blocks + removing or demoting a project's **last** manager; and the `invite_member(project_id, +email, role)` RPC (SECURITY DEFINER, PM-checked) that resolves the email to a user id + and upserts the membership (returns NULL when no account has that email — no + enumeration surface). Backs Phase 5.1. Apply after `0007`. _(Numbering note: ROADMAP + §5.1 sketched `0006_member_policies.sql`, but `0006`/`0007` were taken, so it ships as + `0008`; invite resolves the email inside the RPC rather than via a client-side profiles + lookup, keeping emails/ids server-side.)_ - `0009_workflow.sql` — adds a `project_stage` enum + `projects.stage` (default `translation`) and `projects.deadline`, gating role-based editing (§5.2). Writes are project_manager-only through the existing `projects_update` policy — - no new policy. Backs Phase 5.2. Apply after `0008`. *(Numbering note: ROADMAP + no new policy. Backs Phase 5.2. Apply after `0008`. _(Numbering note: ROADMAP §5.2 sketched `0007_workflow.sql`, but `0007`/`0008` were taken, so it ships as - `0009`.)* + `0009`.)_ > Note: the linter's "Leaked Password Protection Disabled" warning is unrelated > to these migrations — it's an optional **Authentication → Policies** toggle @@ -153,7 +153,7 @@ In the Supabase dashboard under **Authentication → Providers**: ### Redirect URL allow-list (critical) Verbalis is a **hash-routed static site**, so we pin Supabase's **PKCE** flow: -the `?code=` lands in the query string *before* the `#/route` fragment, and a +the `?code=` lands in the query string _before_ the `#/route` fragment, and a pre-router bootstrap (`src/storage/cloud/authBootstrap.ts`) exchanges it and strips the query before React mounts. For this to work, add every origin you serve from to **Authentication → URL Configuration → Redirect URLs**: @@ -284,7 +284,7 @@ project card (`ManageMembersDialog` → `src/storage/cloud/members.ts`). Apply a second signed-in account: - [ ] **PM invites**: as the manager, open Members → invite the second account's - email as *translator*, then again as *revisor* → they appear in the roster + email as _translator_, then again as _revisor_ → they appear in the roster with the chosen role, and can open the project. - [ ] **Unknown email**: inviting an email with no Verbalis account yet shows the "no account uses that email" notice (the RPC returns NULL) — no row added. @@ -308,13 +308,13 @@ first. With a project_manager, a translator, and a revisor on one cloud project: - [ ] **PM sets the stage**: in Members, the manager moves the project through Translation → Review → Final; a non-PM sees the stage read-only. -- [ ] **Translator forced to suggest in review**: with the stage on *Review*, the +- [ ] **Translator forced to suggest in review**: with the stage on _Review_, the translator's edit-mode toggle is locked to **Suggesting** (disabled) and typing produces tracked suggestions, not direct edits. - [ ] **Accept/reject is reviewer-only**: the translator sees suggestions but **no** Accept/Reject (panel buttons hidden, hover card suppressed); the revisor and the PM see and can resolve them. -- [ ] **Final locks editing**: in *Final*, translator + revisor can't edit; only the +- [ ] **Final locks editing**: in _Final_, translator + revisor can't edit; only the PM can (e.g. to reopen by moving the stage back). - [ ] **Local-only unchanged**: a project with no cloud link behaves as "PM-of-self" — direct editing, suggesting, and accept/reject all available, @@ -336,7 +336,7 @@ verification burden. The access token lives in memory only (gone on reload). testing; add yourself as a test user). Add the scope `https://www.googleapis.com/auth/drive.file`. 4. **APIs & Services → Credentials → Create credentials → OAuth client ID → - Web application**. Under *Authorised JavaScript origins* add the exact + Web application**. Under _Authorised JavaScript origins_ add the exact origins the app runs on, e.g. `http://localhost:5173` and `https://verbalis.britx.me`. (No redirect URI is needed — GIS uses the token flow, not a redirect.) @@ -350,8 +350,8 @@ script is loaded and the connector code stays out of the initial bundle. With `VITE_GOOGLE_CLIENT_ID` set: -- [ ] **Addon listed**: the Add-ons page shows **Google Drive** under *Storage - connectors* with Network / API key / Files permission chips and a working +- [ ] **Addon listed**: the Add-ons page shows **Google Drive** under _Storage + connectors_ with Network / API key / Files permission chips and a working on/off toggle. - [ ] **Import from Drive**: in the Import dialog, **From Google Drive** opens the Google consent popup, then a file list; picking a `.docx`/`.txt`/`.md`/XLIFF @@ -396,8 +396,8 @@ never loaded and the connector code stays out of the initial bundle. With `VITE_MS_CLIENT_ID` set: -- [ ] **Addon listed**: the Add-ons page shows **OneDrive** under *Storage - connectors* with Network / API key / Files permission chips and a working +- [ ] **Addon listed**: the Add-ons page shows **OneDrive** under _Storage + connectors_ with Network / API key / Files permission chips and a working on/off toggle. - [ ] **Import from OneDrive**: in the Import dialog, **From OneDrive** opens the Microsoft login popup, then a file list; picking a diff --git a/docs/refactor-audit.md b/docs/refactor-audit.md index 5ed63dc..eb35776 100644 --- a/docs/refactor-audit.md +++ b/docs/refactor-audit.md @@ -13,10 +13,11 @@ everything else — so the rest can be picked up one slice at a time. **Verbalis is in excellent health.** ~30,900 lines across 309 TS/TSX files, a clean feature/core/storage separation, `tsc -b` passes, and **717/717 unit tests -+ ~29 e2e tests are green**. There are **zero** `TODO`/`FIXME`/`HACK` markers, -only 5 `console.*` calls, and disciplined code-splitting keeps the optional -cloud/AI/DOCX code out of the initial bundle. This is not a codebase that needs -rescuing — it needs *organising and finishing touches*. + +- ~29 e2e tests are green**. There are **zero** `TODO`/`FIXME`/`HACK` markers, + only 5 `console.*` calls, and disciplined code-splitting keeps the optional + cloud/AI/DOCX code out of the initial bundle. This is not a codebase that needs + rescuing — it needs _organising and finishing touches_. The findings below are therefore mostly **polish, tooling, and documentation**, not structural rework. They are ranked by value ÷ risk so the highest-leverage, @@ -24,18 +25,18 @@ lowest-danger items come first. ### Health snapshot -| Signal | Value | Notes | -|---|---|---| -| Source size | ~30,900 LOC / 309 files | Well-factored | -| Unit tests | 717 passing / 118 files | Excellent coverage of `core/*` + `storage/*` | -| E2E tests | ~29 (Playwright) | Cover the real user journeys | -| Typecheck | ✅ clean | Strict mode | -| `TODO`/`FIXME`/`HACK` | 0 | — | -| `console.*` | 5 | Acceptable; a couple could be dropped | -| `any` / `ts-ignore` | 22 | Mostly legit (markdown/docx AST walkers) | -| Dexie schema | v7 | Migrations are additive & tested | -| Linter | ❌ none | See §3.1 | -| Bundler | Vite 6, aggressive lazy chunks | Cloud/AI/DOCX all code-split | +| Signal | Value | Notes | +| --------------------- | ------------------------------ | -------------------------------------------- | +| Source size | ~30,900 LOC / 309 files | Well-factored | +| Unit tests | 717 passing / 118 files | Excellent coverage of `core/*` + `storage/*` | +| E2E tests | ~29 (Playwright) | Cover the real user journeys | +| Typecheck | ✅ clean | Strict mode | +| `TODO`/`FIXME`/`HACK` | 0 | — | +| `console.*` | 5 | Acceptable; a couple could be dropped | +| `any` / `ts-ignore` | 22 | Mostly legit (markdown/docx AST walkers) | +| Dexie schema | v7 | Migrations are additive & tested | +| Linter | ❌ none | See §3.1 | +| Bundler | Vite 6, aggressive lazy chunks | Cloud/AI/DOCX all code-split | --- @@ -75,7 +76,7 @@ tests): **3.1 Add a linter (and wire it into CI).** There is no ESLint config, no `lint` script, and no eslint dependency. The code -is consistent today (clearly hand-disciplined), but nothing *enforces* it — no +is consistent today (clearly hand-disciplined), but nothing _enforces_ it — no guard against unused vars/imports, `react-hooks/exhaustive-deps` mistakes, floating promises, or accidental `console.log`s. Add a flat-config ESLint (`typescript-eslint`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`) @@ -84,8 +85,7 @@ plus a `pnpm lint` step in `ci.yml`. Low risk, compounding value. **3.2 Pin the package manager.** `package.json` has no `packageManager` field, and its `pnpm.onlyBuiltDependencies` lives in the location **pnpm 11 no longer reads** (it warns and ignores it), while -CI pins `pnpm/action-setup@v3` to v9. So a fresh local checkout (corepack → pnpm -11) and CI (pnpm 9) resolve builds differently. Add `"packageManager": "pnpm@9.x"` +CI pins `pnpm/action-setup@v3` to v9. So a fresh local checkout (corepack → pnpm 11) and CI (pnpm 9) resolve builds differently. Add `"packageManager": "pnpm@9.x"` (matching CI) and move `onlyBuiltDependencies` to `pnpm-workspace.yaml` if you bump to pnpm ≥10. Reconcile the two workflow files at the same time (action-setup warns if both `version:` and `packageManager` are set). @@ -134,6 +134,7 @@ The 22 `any`/`ts-ignore` sites are concentrated in `core/documents/toDocx.ts` an node type. Not urgent, but it removes the last untyped seams. **3.9 Accessibility & UX polish.** + - The theme toggle and several icon buttons rely on `aria-label` only — good, but a visible focus ring audit across the new hero + cards is worth one pass. - Consider `prefers-reduced-motion` for the sidebar/topbar transitions (the CSS @@ -145,7 +146,7 @@ node type. Not urgent, but it removes the last untyped seams. ### P3 — larger / strategic (maintainer's call) **3.10 A dedicated marketing landing page.** -Today the app *is* the landing page (Excalidraw-style), and the elevated welcome +Today the app _is_ the landing page (Excalidraw-style), and the elevated welcome hero now serves that well for first-run. If you want SEO/marketing reach (feature tour, screenshots, pricing, testimonials) without loading the app, a separate static route or a small companion site is the way — but it is a product decision, @@ -162,19 +163,19 @@ were explicitly deferred (see `architecture.md` Phase 6). Natural next addons. ## 4. Documentation inventory -| Path | Role | Recommendation | -|---|---|---| -| `README.md` | Front door | ✅ refreshed this pass | -| `docs/architecture.md` | Living architecture | ✅ refreshed; keep current | -| `docs/cloud.md` | Cloud/signed-in setup | Living; keep | -| `docs/refactor-audit.md` | This file | Living backlog | -| `docs/revamp/ROADMAP.md` + `STATUS.md` | Revamp record | Keep as history; link from architecture | -| `docs/revamp/prompts/*` (30 files) | Build scaffolding | Archive or delete — served their purpose | -| `docs/vision/*` (6 files) | Pre-build product vision | Mark historical; superseded by shipped app | -| `docs/roadmap-professional-features.md` | Pre-revamp plan | Mark historical; largely delivered | +| Path | Role | Recommendation | +| --------------------------------------- | ------------------------ | ------------------------------------------ | +| `README.md` | Front door | ✅ refreshed this pass | +| `docs/architecture.md` | Living architecture | ✅ refreshed; keep current | +| `docs/cloud.md` | Cloud/signed-in setup | Living; keep | +| `docs/refactor-audit.md` | This file | Living backlog | +| `docs/revamp/ROADMAP.md` + `STATUS.md` | Revamp record | Keep as history; link from architecture | +| `docs/revamp/prompts/*` (30 files) | Build scaffolding | Archive or delete — served their purpose | +| `docs/vision/*` (6 files) | Pre-build product vision | Mark historical; superseded by shipped app | +| `docs/roadmap-professional-features.md` | Pre-revamp plan | Mark historical; largely delivered | The revamp is done, so several planning docs are now historical rather than -directive. Nothing here is *wrong* — it just needs a clear "this was the plan / +directive. Nothing here is _wrong_ — it just needs a clear "this was the plan / this is the current state" boundary so a newcomer isn't misled. --- diff --git a/docs/revamp/ROADMAP.md b/docs/revamp/ROADMAP.md index 93488ce..fca69dc 100644 --- a/docs/revamp/ROADMAP.md +++ b/docs/revamp/ROADMAP.md @@ -20,26 +20,26 @@ This roadmap delivers the **web version**; desktop (Tauri) stays on the roadmap - **Roles per project**: `project_manager` / `translator` / `revisor`, enforced by Postgres RLS where enforceable and by UI gating elsewhere. - **Extensions & plugins**: built-in addon registry first (MT providers, QA rules, formats become addons), Google Drive + OneDrive connectors; marketplace later. -The repo already contains real foundations to **extend, not replace**: per-project Yjs docs (y-indexeddb), Dexie↔Yjs bridges with Dexie as local source of truth, a tiny `SyncTransport` seam (`src/storage/sync/transport/types.ts`), version snapshots + per-segment history + read-only changes panel with author colours (`src/storage/repositories/versionRepo.ts`, `src/core/history/diff.ts`), per-segment un-anchored comments, and the opt-in per-segment Lexical editor with the load-bearing contract *"Lexical state is truth, `richStateToPlain()` derives plain `target`"* (`src/core/editor/richText.ts` — it uses `$getRoot().getTextContent()`, so mark nodes overriding `getTextContent()` shape plain text in both live and headless paths automatically). Genuinely new axes: real document/block model, in-editor accept/reject change tracking, Supabase identity + cloud transport, roles, addons. +The repo already contains real foundations to **extend, not replace**: per-project Yjs docs (y-indexeddb), Dexie↔Yjs bridges with Dexie as local source of truth, a tiny `SyncTransport` seam (`src/storage/sync/transport/types.ts`), version snapshots + per-segment history + read-only changes panel with author colours (`src/storage/repositories/versionRepo.ts`, `src/core/history/diff.ts`), per-segment un-anchored comments, and the opt-in per-segment Lexical editor with the load-bearing contract _"Lexical state is truth, `richStateToPlain()` derives plain `target`"_ (`src/core/editor/richText.ts` — it uses `$getRoot().getTextContent()`, so mark nodes overriding `getTextContent()` shape plain text in both live and headless paths automatically). Genuinely new axes: real document/block model, in-editor accept/reject change tracking, Supabase identity + cloud transport, roles, addons. ## 2. Locked decisions (owner-confirmed) -| Decision | Choice | -| --- | --- | -| Backend | **Supabase** (Auth + Postgres/RLS + Realtime + Storage); strictly additive, local-only mode never loads it | -| v1 cloud connectors | **Google Drive + OneDrive** (storage-connector addons); iCloud deferred to desktop phase | -| Build automation | **Prompt pack in this folder + a scheduled Routine** firing autonomous sessions, one phase per session, each ending in a draft PR | -| Rate budget | **Claude Pro** → small surgical phases (one slice, ≤ ~15–20 files, tests included), max 1 phase/day | +| Decision | Choice | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| Backend | **Supabase** (Auth + Postgres/RLS + Realtime + Storage); strictly additive, local-only mode never loads it | +| v1 cloud connectors | **Google Drive + OneDrive** (storage-connector addons); iCloud deferred to desktop phase | +| Build automation | **Prompt pack in this folder + a scheduled Routine** firing autonomous sessions, one phase per session, each ending in a draft PR | +| Rate budget | **Claude Pro** → small surgical phases (one slice, ≤ ~15–20 files, tests included), max 1 phase/day | ## 3. Architecture decisions - **D1 — Tracked changes via Lexical mark nodes per segment cell, not a document-wide editor rewrite.** A custom `ChangeMarkNode` (modeled on `@lexical/mark`'s `MarkNode`, following the `InlineTagNode` precedent) is incremental, serializes inside `targetRich`, and survives versioning/restore for free. A document-wide editor would invalidate the entire per-segment stack (autosave, TM/QA, CRDT, XLIFF chips). - **D2 — Plain-text semantics: `target` = "proposed text"** (pending insertions included, pending deletions excluded), implemented by `ChangeMarkNode.getTextContent() → ''` for deletes — the same trick `InlineTagNode` uses. Live editor and headless `richStateToPlain` agree with zero changes to TM/QA/search call sites. A pure `richStateToOriginal()` walker (exclude inserts, include deletes) feeds diff rendering. Confirm-to-TM is gated on "no pending changes" (Phase 1.6) so suggestions never contaminate the TM. - **D3 — Change metadata inline in the mark attrs** (`{changeId, type: insert|delete, authorId, authorName, createdAt}`); no parallel structure to keep consistent. Panels use a pure `extractChanges(targetRich)`. Author colours reuse `src/core/history/authorColor.ts`. -- **D4 — Collab granularity: per-segment edit leases + LWW `targetRich`**; character-level same-segment rich co-editing (`@lexical/yjs`/`Y.XmlText`) is roadmap. Leases (lowest-peerId tie-break) match CAT-industry row-locking norms; concurrent edits on *different* segments already merge perfectly. +- **D4 — Collab granularity: per-segment edit leases + LWW `targetRich`**; character-level same-segment rich co-editing (`@lexical/yjs`/`Y.XmlText`) is roadmap. Leases (lowest-peerId tie-break) match CAT-industry row-locking norms; concurrent edits on _different_ segments already merge perfectly. - **D5 — Document model: new `documents` + `blocks` Dexie tables above flat segments**; segments stay the unit of translation/TM/QA/sync and gain an unindexed `blockId`. Existing projects backfilled from `sourceMeta.blockIndex`; XLIFF projects get no document tree (round-trip untouched). - **D6 — Supabase strictly additive**: env-flag + dynamic `import()` — zero bundle cost when unset. Cloud collab source of truth = Yjs blobs (`ydoc_state` snapshot + `ydoc_updates` append log with `author_id`); structured rows only where Postgres must query/enforce (`profiles`, `user_settings`, `projects`, `project_members`, `personal_termbank`/`personal_tm`). -- **D7 — Auth: Supabase PKCE flow** (`flowType: 'pkce'` pinned) — the `?code=` query lands *before* the `#/route` fragment, so HashRouter never sees it; a pre-router bootstrap exchanges the code and strips the query. Native Supabase Auth covers Google/Apple/Azure OAuth + magic links; **passkeys are NOT native Supabase Auth** → honest fast-follow (Supabase roadmap or Edge-Function WebAuthn), not advertised in v1 UI. +- **D7 — Auth: Supabase PKCE flow** (`flowType: 'pkce'` pinned) — the `?code=` query lands _before_ the `#/route` fragment, so HashRouter never sees it; a pre-router bootstrap exchanges the code and strips the query. Native Supabase Auth covers Google/Apple/Azure OAuth + magic links; **passkeys are NOT native Supabase Auth** → honest fast-follow (Supabase roadmap or Edge-Function WebAuthn), not advertised in v1 UI. - **D8 — Roles: RLS enforces the enforceable** (membership, write access, PM-only management, `author_id = auth.uid()` on every update row → tamper-evident attribution). Content-level workflow rules (translator-must-suggest) are client-enforced with the authored append-only update log as audit trail; server-side validation is roadmap. Stated honestly in docs. - **D9 — Extensions v1: in-process typed registry with real manifests/permissions**; sandboxed (iframe/worker RPC) hosting deferred but the manifest contract is final now, so extension authors never migrate. @@ -48,62 +48,73 @@ The repo already contains real foundations to **extend, not replace**: per-proje Sizing: **S** ≈ ≤8 files; **M** ≈ 10–20 files. Every phase ends green: `pnpm typecheck && pnpm test:unit && pnpm build` (+ Playwright where UI-facing), branch `claude/revamp-phase-N-N`, draft PR, [`STATUS.md`](./STATUS.md) updated in the same PR. ### Phase 0 — Bootstrap (this commit) + Roadmap, status checklist, prompt pack, and vision docs committed to the repo. ### Milestone 0 — Guardrails + - **0.1 CI hardening — S — deps: none.** Create `.github/workflows/ci.yml` (PR + non-main push: install → generate-icons → `tsc -b` → `vitest run` → build → Playwright chromium). Modify `playwright.config.ts` (CI retries/reporter), `package.json` (`test:unit`, `typecheck` scripts). Keep `deploy.yml` untouched; upload traces on failure. **DoD**: PRs show a green check running unit + e2e. ### Milestone 1 — Inline tracked changes + anchored comments (flagship; pure client, no backend) + - **1.1 Change model core + `ChangeMarkNode` + derivation semantics — M — deps: 0.1.** Create `src/core/changes/model.ts`, `src/core/changes/extract.ts`, `src/features/editor/rich/ChangeMarkNode.ts` (attrs per D3; `getTextContent()→''` for deletes; strikethrough/underline + author colour in `createDOM`). Modify `src/core/editor/richText.ts` (add `richStateToOriginal`), `src/styles/globals.css`. Tests: model/extract round-trips; plain derivation per D2. **DoD**: hand-built `targetRich` with marks renders styled; derivation semantics proven; existing tests pass. - **1.2 Rich editing default-on + Playwright migration — M — deps: 1.1.** Flip `DEFAULT_EDITOR_SETTINGS.richEditing` to true (`src/storage/repositories/settingsRepo.ts`); migrate all 7 e2e specs off `textarea.fill()` to contenteditable helpers (`tests/e2e/helpers/richEditor.ts`); plain stays for code segments + opt-out. **DoD**: full e2e suite green with rich default; opt-out works. - **1.3 Suggesting mode — M — deps: 1.2.** Create `src/core/changes/suggest.ts` ($-helpers: insert/delete-as-suggestion, mark split/merge), `src/features/editor/rich/TrackedChangesPlugin.tsx` (intercept `CONTROLLED_TEXT_INSERTION`/`PASTE`/`CUT`/`KEY_BACKSPACE`/`KEY_DELETE` when suggesting; IME: act only on final insertions post-composition — top correctness risk). Modify `useEditorModeStore` (`editMode: direct|suggesting`), `RichSegmentEditor`, toolbar/palette toggle, `settingsRepo` (stable `authorId` uuid in `profile.identity`). **DoD**: no keystroke destroys prior text in suggesting mode; `target` follows D2; direct mode byte-identical to today. -- **1.4 Accept/reject + Changes panel rework — M — deps: 1.3.** Create `src/core/changes/resolve.ts` (live `$accept/$rejectChange` + headless `resolveChangeInRich` for unmounted segments), `src/features/editor/rich/ChangeHoverCard.tsx`. Rework `changes/ChangesPanel.tsx` to list *pending* suggestions live (old version-diff view becomes a "History" sub-tab); `segmentRepo.resolveChange` routes through `segmentRepo.update` so bridges/versioning see it. Accept-all/reject-all per segment & project. **DoD**: full suggest → review → accept/reject loop works locally. +- **1.4 Accept/reject + Changes panel rework — M — deps: 1.3.** Create `src/core/changes/resolve.ts` (live `$accept/$rejectChange` + headless `resolveChangeInRich` for unmounted segments), `src/features/editor/rich/ChangeHoverCard.tsx`. Rework `changes/ChangesPanel.tsx` to list _pending_ suggestions live (old version-diff view becomes a "History" sub-tab); `segmentRepo.resolveChange` routes through `segmentRepo.update` so bridges/versioning see it. Accept-all/reject-all per segment & project. **DoD**: full suggest → review → accept/reject loop works locally. - **1.5 Range-anchored threaded comments — M — deps: 1.2 (parallel to 1.3/1.4).** Create `CommentMarkNode` (thin `MarkNode` subclass), `comments/CommentsPanel.tsx` (project-wide, filter, click-to-jump). Extend `SegmentComment` with `parentId?/anchorId?/quote?` (additive), carry through `segmentCrdt.ts` comment mapping; "comment on selection" in toolbar; resolve dims/removes mark. Comments stay inline on segments (existing Dexie + Y.Array path — zero new sync work). **DoD**: select → comment → highlighted range + thread; replies/resolve; survives Yjs round-trip + restore. -- **1.6 Review polish — S — deps: 1.4, 1.5.** Confirm blocked while pending changes exist (toast); next/prev-change keyboard nav + palette commands; revision stage pins Changes/Comments panels; show/hide marks toggle. **DoD**: pending segments can't reach TM; keyboard review works. *Milestone 1 = Google-Docs-style review, fully local.* +- **1.6 Review polish — S — deps: 1.4, 1.5.** Confirm blocked while pending changes exist (toast); next/prev-change keyboard nav + palette commands; revision stage pins Changes/Comments panels; show/hide marks toggle. **DoD**: pending segments can't reach TM; keyboard review works. _Milestone 1 = Google-Docs-style review, fully local._ ### Milestone 2 — Document model, DOCX fidelity, preview, DOCX export + - **2.1 Document/block schema (Dexie v6) + backfill — M — deps: 0.1.** Create `src/core/documents/model.ts` (`DocumentEntity`, `Block` with kind/depth/attrs/`InlineRun[]` source runs, tables/images), `documentRepo`/`blockRepo`, `fromSegments.ts` backfill. Modify `db.ts` (v6: `documents`, `blocks`, `assets`; upgrade backfills monolingual projects), `Segment.blockId?`, `segmentCrdt.ts` SCALAR_FIELDS, import flow stamps `blockId`. Blocks not mirrored to Yjs in v1 (immutable post-import). **DoD**: fresh + upgraded projects have consistent block trees; no UI change. - **2.2 DOCX import fidelity — M — deps: 2.1.** Create `src/core/documents/docxImport.ts`: mammoth with explicit `styleMap` + `convertImage` → assets; walker captures strong/em/u/sub/sup/a runs, tables, images. `segmentation/docx.ts` delegates, keeping the `ParsedSegment` contract. Store original .docx blob in `assets` for future re-import. Fixture-based tests. **DoD**: styled DOCX yields blocks with runs/tables/images; segment output strictly richer than before. - **2.3 Document preview pane — M — deps: 2.1.** `preview/DocumentPreview.tsx` + pure `core/documents/render.ts`: block tree with translated targets substituted (fallback source + untranslated tint), source/target/side-by-side, click-block→focus-segment. **DoD**: live assembled translated document while editing. -- **2.4 Clean DOCX export — M — deps: 2.2, 2.3.** `core/documents/toDocx.ts` (dynamic `import('docx')`): blocks + `targetRich` formatting runs → headings/lists/links/tables/images. Pending changes export as accepted-preview (D2) with warning. Test by unzipping the buffer and asserting `document.xml`. **DoD**: import → translate → export opens correctly in Word. *Milestone 2 = document revamp, fully local.* +- **2.4 Clean DOCX export — M — deps: 2.2, 2.3.** `core/documents/toDocx.ts` (dynamic `import('docx')`): blocks + `targetRich` formatting runs → headings/lists/links/tables/images. Pending changes export as accepted-preview (D2) with warning. Test by unzipping the buffer and asserting `document.xml`. **DoD**: import → translate → export opens correctly in Word. _Milestone 2 = document revamp, fully local._ ### Milestone 3 — Accounts (Supabase) + synced preferences & personal resources + - **3.1 Supabase bootstrap + Google/magic-link auth + PKCE/HashRouter handling — M — deps: 0.1.** Create `src/storage/cloud/supabaseClient.ts` (lazy singleton, `flowType:'pkce'`, only when `VITE_SUPABASE_URL` set), `authBootstrap.ts` (pre-router `?code=` exchange + `history.replaceState`), `features/account/` (`useAuthStore`, `SignInDialog`, `AccountMenu`), `supabase/migrations/0001_profiles.sql` (profiles + RLS + signup trigger), `docs/cloud.md`. Modify `main.tsx` (await bootstrap), `TopBar`, CI (build once with dummy env). Signed-out behavior byte-identical; no supabase chunk in initial graph when unset. **DoD**: OAuth + magic link round-trip on the deployed hash-routed site. - **3.2 Microsoft + Apple providers + account settings — S — deps: 3.1.** Provider buttons driven by `VITE_AUTH_PROVIDERS` (deploys without Apple's paid setup hide the button, not break). `AccountSettingsSection` (display name → profiles, linked identities, sign out). Document Azure app registration + Apple Developer Program requirement. **DoD**: configured providers work; unconfigured are hidden. -- **3.3 Synced preferences/settings/layout — M — deps: 3.1.** `0002_user_settings.sql` (`user_settings(user_id,key,value jsonb,updated_at)` RLS owner-only); `cloud/settingsSync.ts` — pull-on-signin, push-on-change, per-key LWW; `SYNCED_SETTINGS_KEYS` allowlist (editor prefs, layout, lookup, spell; **MT API keys never sync**). Dexie stays the read path — sync is a background reconciler. **DoD**: sign in on machine B → prefs/layout arrive; local-only unaffected. *Shipped as `0003_user_settings.sql` (0002 was taken by profiles hardening). Layout deferred to 3.3.1 (see below).* -- **3.3.1 Sidebar layout sync — S — deps: 3.3.** Fold the sidebar layout (`useSidebarPanelStore`, a zustand `persist` store in localStorage) into the 3.3 reconciler. Add an LWW `updatedAt` to its persisted state (stamped on each layout/collapsed mutation), register it as a `settingsSync` adapter (the engine is already generic over read/write/timestamp), and rehydrate the live store after a pull so machine B reflects the layout without a reload. **DoD**: reorder/collapse panels on machine A → arrives on machine B; local-only unaffected. *Shipped as a dedicated `features/editor/layoutSync.ts` reusing the `user_settings` table + LWW (the 3.3 `settingsSync` engine is bound to Dexie `settingsRepo` keys, not a source-agnostic adapter engine as this note optimistically implied) — the layout is a zustand/localStorage store, so it syncs as a sibling reconciler rather than a settingsSync adapter.* -- **3.4 Personal term bank + TM sync — M — deps: 3.3.** `0003_personal_resources.sql` (owner-only rows + tombstones); generic `cloud/rowSync.ts` (cursor, LWW, tombstones); Dexie v7 `syncTombstones` table; glossary/tm repos stamp `updatedAt` + tombstone deletes when signed in; bundled corpora never sync. **DoD**: two devices converge; deletes don't resurrect. *Milestone 3 = accounts shipped.* *Shipped as `0004_personal_resources.sql` (0003 was taken by user_settings); soft-delete `deleted` column on the cloud rows carries tombstones server-side.* +- **3.3 Synced preferences/settings/layout — M — deps: 3.1.** `0002_user_settings.sql` (`user_settings(user_id,key,value jsonb,updated_at)` RLS owner-only); `cloud/settingsSync.ts` — pull-on-signin, push-on-change, per-key LWW; `SYNCED_SETTINGS_KEYS` allowlist (editor prefs, layout, lookup, spell; **MT API keys never sync**). Dexie stays the read path — sync is a background reconciler. **DoD**: sign in on machine B → prefs/layout arrive; local-only unaffected. _Shipped as `0003_user_settings.sql` (0002 was taken by profiles hardening). Layout deferred to 3.3.1 (see below)._ +- **3.3.1 Sidebar layout sync — S — deps: 3.3.** Fold the sidebar layout (`useSidebarPanelStore`, a zustand `persist` store in localStorage) into the 3.3 reconciler. Add an LWW `updatedAt` to its persisted state (stamped on each layout/collapsed mutation), register it as a `settingsSync` adapter (the engine is already generic over read/write/timestamp), and rehydrate the live store after a pull so machine B reflects the layout without a reload. **DoD**: reorder/collapse panels on machine A → arrives on machine B; local-only unaffected. _Shipped as a dedicated `features/editor/layoutSync.ts` reusing the `user_settings` table + LWW (the 3.3 `settingsSync` engine is bound to Dexie `settingsRepo` keys, not a source-agnostic adapter engine as this note optimistically implied) — the layout is a zustand/localStorage store, so it syncs as a sibling reconciler rather than a settingsSync adapter._ +- **3.4 Personal term bank + TM sync — M — deps: 3.3.** `0003_personal_resources.sql` (owner-only rows + tombstones); generic `cloud/rowSync.ts` (cursor, LWW, tombstones); Dexie v7 `syncTombstones` table; glossary/tm repos stamp `updatedAt` + tombstone deletes when signed in; bundled corpora never sync. **DoD**: two devices converge; deletes don't resurrect. _Milestone 3 = accounts shipped._ _Shipped as `0004_personal_resources.sql` (0003 was taken by user_settings); soft-delete `deleted` column on the cloud rows carries tombstones server-side._ ### Milestone 4 — Cloud projects + real-time collaboration + - **4.1 Cloud project schema + RLS + publish/join — M — deps: 3.1.** `0004_projects.sql`: `projects`, `project_members(role enum: project_manager|translator|revisor)`, `ydoc_state(state bytea, seq)`, `ydoc_updates(update bytea, author_id default auth.uid())`, member-scoped RLS + `is_project_member`/`has_role` helpers, `project-files` bucket. `cloud/projectCloud.ts`: `publishProject` (seeds cloud from local Yjs doc — pre-publish history survives), `openCloudProject` (hydrates local Dexie project). `Project.cloud?: {id, role}`. Publish is owner-only UX; joining is member-only (prevents duplicate cloud projects). **DoD**: publish; second account opens it; non-members RLS-denied. - **4.2 `SupabaseRealtimeTransport` + chunking — M — deps: 4.1.** Implements the existing `SyncTransport` interface unchanged (`start/send/onMessage/destroy`, `hello/bye/state-request/state/update/presence`). Private broadcast channel `project:{cloudId}`; pure `chunking.ts` splits payloads >60KB (Realtime caps ~250KB; base64 +33%); outbound updates debounce-merged via `Y.mergeUpdates` (300ms). Initial state comes from Postgres (4.3), never broadcast for large docs. Transport picked in `createTransport` when project is cloud + signed in; BroadcastChannel/LAN paths untouched. **DoD**: two browsers exchange live edits; protocol unit-covered. - **4.3 Postgres persistence loop — M — deps: 4.2.** `cloud/ydocPersistence.ts`: on open, fetch `ydoc_state` + newer updates, apply as `ORIGIN_REMOTE`; local updates appended debounced; client-side compaction >200 rows via optimistic `claim_compaction(project_id, expected_seq)` RPC (`0005_compaction.sql`). Realtime is latency, Postgres is truth; offline members converge by replay (Yjs idempotent). **DoD**: offline-A/online-B edits converge on reconnect; updates table bounded. -- **4.4 Live collab UX: cursors, leases, attribution — M — deps: 4.3, 1.6.** Presence gains `editingSegmentId`/`userId`/`caret`; deterministic lease (lowest peerId) renders other peers' segment read-only with name chip + live preview; `RemoteCaretOverlay` reuses offset→DOM mapping from `src/core/spell/offsets.ts`. Remote suggesting-mode edits arrive pre-attributed via `ChangeMarkNode` — "everyone's changes tracked" falls out of M1 + sync. Guard in `reverseBridge.ts`: rebuild rich from plain on detected stale-LWW mismatch. **DoD**: live cursors; simultaneous entry yields one editor + one viewer; lease releases on blur. *Milestone 4 = real-time collaboration shipped.* *Shipped the lease/attribution/guard core (`sync/lease.ts`, presence `userId`+`caret`, `SegmentRow` read-only + "is editing" chip, reverseBridge stale-LWW guard); the **live remote-caret overlay is deferred to 4.4.1** — the presence model carries `caret` as the seam, but rendering it needs editor→session caret reporting + offset→DOM measurement, a separable slice.* -- **4.4.1 Remote caret overlay — S — deps: 4.4.** Report the local caret (Lexical selection → plain-text offset) through `setActiveSegment(id, caret)`, and render remote peers' carets/selections in each segment with a `RemoteCaretOverlay` reusing the offset→DOM mapping in `src/core/spell/offsets.ts` (as `SpellUnderlinePlugin` does). **DoD**: live remote cursors move as peers type; overlay never mutates the editor tree. *Shipped: `mapOffsetToNode` (core) + `richOffsets.ts` (`selectionToCaretRange`/`buildLeafSpans`, reproducing the plain projection incl. delete-mark → '') + `RemoteCaretOverlay`; the focused editor reports its caret throttled to 100ms. **Milestone 4 complete.*** +- **4.4 Live collab UX: cursors, leases, attribution — M — deps: 4.3, 1.6.** Presence gains `editingSegmentId`/`userId`/`caret`; deterministic lease (lowest peerId) renders other peers' segment read-only with name chip + live preview; `RemoteCaretOverlay` reuses offset→DOM mapping from `src/core/spell/offsets.ts`. Remote suggesting-mode edits arrive pre-attributed via `ChangeMarkNode` — "everyone's changes tracked" falls out of M1 + sync. Guard in `reverseBridge.ts`: rebuild rich from plain on detected stale-LWW mismatch. **DoD**: live cursors; simultaneous entry yields one editor + one viewer; lease releases on blur. _Milestone 4 = real-time collaboration shipped._ _Shipped the lease/attribution/guard core (`sync/lease.ts`, presence `userId`+`caret`, `SegmentRow` read-only + "is editing" chip, reverseBridge stale-LWW guard); the **live remote-caret overlay is deferred to 4.4.1** — the presence model carries `caret` as the seam, but rendering it needs editor→session caret reporting + offset→DOM measurement, a separable slice._ +- **4.4.1 Remote caret overlay — S — deps: 4.4.** Report the local caret (Lexical selection → plain-text offset) through `setActiveSegment(id, caret)`, and render remote peers' carets/selections in each segment with a `RemoteCaretOverlay` reusing the offset→DOM mapping in `src/core/spell/offsets.ts` (as `SpellUnderlinePlugin` does). **DoD**: live remote cursors move as peers type; overlay never mutates the editor tree. _Shipped: `mapOffsetToNode` (core) + `richOffsets.ts` (`selectionToCaretRange`/`buildLeafSpans`, reproducing the plain projection incl. delete-mark → '') + `RemoteCaretOverlay`; the focused editor reports its caret throttled to 100ms. **Milestone 4 complete.**_ ### Milestone 5 — Roles & workflow -- **5.1 Members & roles management — M — deps: 4.1.** `cloud/members.ts` (invite by email via profiles lookup, change role, remove), `MembersPanel`, `0006_member_policies.sql` (PM-only member writes; trigger keeps ≥1 PM). **DoD**: PM invites translator + revisor; non-PM read-only; server rejects non-PM writes. *Shipped as `0008_member_policies.sql` (0006/0007 taken) + `cloud/members.ts` + a `ManageMembersDialog` on the project card (lazy-loaded) instead of a sidebar `MembersPanel`; invite resolves the email inside the SECURITY DEFINER `invite_member` RPC (PM-checked, no enumeration surface) rather than a client-side profiles lookup; `profiles.email` + a co-member SELECT policy added; a `enforce_min_one_pm` trigger guards the last manager.* -- **5.2 Role-gated editing workflow — M — deps: 5.1, 1.6.** Pure `core/workflow/rules.ts`: `(role, stage, status) → {canEditDirect, mustSuggest, canResolveChanges, canReview, canManage}`; `projects.stage (translation|review|final)` + `deadline` (`0007_workflow.sql`, PM-only update). Translator in review stage is forced into suggesting (toggle disabled); accept/reject visible only to revisor/PM; local-only projects = PM-of-self (all permissive, zero regression). **DoD**: full role matrix works; local-only unchanged. *Shipped: pure `core/workflow/rules.ts` + `useWorkflowStore` gating (forced-suggest toggle, reviewer-only accept/reject in `ChangesPanel`/`ChangeHoverCard`), `0009_workflow.sql` (`project_stage` + `deadline`, PM-only via existing `projects_update`), `Project.cloud.stage` read on open + `changeProjectStage`, and a PM stage selector in `ManageMembersDialog`.* -- **5.3 Approval + attribution in versioning — S — deps: 5.2.** `ProjectVersion.authorId?/approval?`; revisor/PM "Sign off" creates a labeled approval version; one stable author identity across marks, comments, versions, presence. **DoD**: sign-off flow; attribution unified. *Milestone 5 = roles shipped → **web v2 launch candidate**.* *Shipped: `ProjectVersion.authorId`/`approval`; `versionRepo` stamps `authorId` (the shared `ensureLocalAuthor` identity) on every capture + a `signOff()` that records a labeled approval version; `useCanReview` gates a **Sign off** button in `VersionHistoryPanel`, which badges approval versions. No migration (versions are local Dexie). **Milestone 5 complete.*** + +- **5.1 Members & roles management — M — deps: 4.1.** `cloud/members.ts` (invite by email via profiles lookup, change role, remove), `MembersPanel`, `0006_member_policies.sql` (PM-only member writes; trigger keeps ≥1 PM). **DoD**: PM invites translator + revisor; non-PM read-only; server rejects non-PM writes. _Shipped as `0008_member_policies.sql` (0006/0007 taken) + `cloud/members.ts` + a `ManageMembersDialog` on the project card (lazy-loaded) instead of a sidebar `MembersPanel`; invite resolves the email inside the SECURITY DEFINER `invite_member` RPC (PM-checked, no enumeration surface) rather than a client-side profiles lookup; `profiles.email` + a co-member SELECT policy added; a `enforce_min_one_pm` trigger guards the last manager._ +- **5.2 Role-gated editing workflow — M — deps: 5.1, 1.6.** Pure `core/workflow/rules.ts`: `(role, stage, status) → {canEditDirect, mustSuggest, canResolveChanges, canReview, canManage}`; `projects.stage (translation|review|final)` + `deadline` (`0007_workflow.sql`, PM-only update). Translator in review stage is forced into suggesting (toggle disabled); accept/reject visible only to revisor/PM; local-only projects = PM-of-self (all permissive, zero regression). **DoD**: full role matrix works; local-only unchanged. _Shipped: pure `core/workflow/rules.ts` + `useWorkflowStore` gating (forced-suggest toggle, reviewer-only accept/reject in `ChangesPanel`/`ChangeHoverCard`), `0009_workflow.sql` (`project_stage` + `deadline`, PM-only via existing `projects_update`), `Project.cloud.stage` read on open + `changeProjectStage`, and a PM stage selector in `ManageMembersDialog`._ +- **5.3 Approval + attribution in versioning — S — deps: 5.2.** `ProjectVersion.authorId?/approval?`; revisor/PM "Sign off" creates a labeled approval version; one stable author identity across marks, comments, versions, presence. **DoD**: sign-off flow; attribution unified. _Milestone 5 = roles shipped → **web v2 launch candidate**._ _Shipped: `ProjectVersion.authorId`/`approval`; `versionRepo` stamps `authorId` (the shared `ensureLocalAuthor` identity) on every capture + a `signOff()` that records a labeled approval version; `useCanReview` gates a **Sign off** button in `VersionHistoryPanel`, which badges approval versions. No migration (versions are local Dexie). **Milestone 5 complete.**_ ### Milestone 6 — Extensions & connectors -- **6.1 Extension registry + MT providers as built-in addons — M — deps: 0.1.** `core/extensions/types.ts` (`ExtensionManifest {id, name, version, kinds, permissions, builtIn}`; kinds: `mt-provider|qa-rule|panel|import-format|export-format|storage-connector`), `registry.ts` (enablement persisted in settings), `builtins.ts`; `src/extensions/mt/*` manifest wrappers delegating to existing `src/core/mt/*` (no logic moves); `mt/index.ts` resolves via registry. Existing MT tests untouched prove behavior preservation. **DoD**: four MT providers run through registry; disabling removes from panel. *Shipped: `core/extensions/{types,registry,builtins}.ts` (dependency-free singleton, enablement as a disabled set, `subscribe`) + `src/extensions/mt` wrappers; `core/mt` `enabledProviders` + `useMTSettings` AND the user setting with the registry (reactive via `useSyncExternalStore`); disabled set persisted under the device-local `extensions.disabled` key, registered from `main.tsx`. No Add-ons page yet (6.2).* -- **6.2 QA rules + formats as addons + Add-ons page — M — deps: 6.1.** Wrap `QA_CODES` rules and XLIFF/TMX/TBX/CSV/DOCX-export as contributions; `features/addons/AddonsPage.tsx` (catalogue, enable/disable, permission display, "suggested/built-in" UX); `/addons` route + nav. QA falls back to all-on when registry empty (keeps tests). **DoD**: Add-ons page lists everything pluggable; toggling a QA addon changes QA output. *Shipped: QA (`qa.`) + format (`format.*`) manifests in `core/extensions/builtins`; `core/qa/registryRules` (`effectiveQaRules` ANDs toggles with the registry, defaults on when unregistered) wired into `QAPanel` (reactive); `features/addons/AddonsPage` at `/addons` (+ nav) grouping addons by kind with permission chips + built-in badges — MT + QA toggle (enforced), formats shown Always-on (format-level gating deferred).* + +- **6.1 Extension registry + MT providers as built-in addons — M — deps: 0.1.** `core/extensions/types.ts` (`ExtensionManifest {id, name, version, kinds, permissions, builtIn}`; kinds: `mt-provider|qa-rule|panel|import-format|export-format|storage-connector`), `registry.ts` (enablement persisted in settings), `builtins.ts`; `src/extensions/mt/*` manifest wrappers delegating to existing `src/core/mt/*` (no logic moves); `mt/index.ts` resolves via registry. Existing MT tests untouched prove behavior preservation. **DoD**: four MT providers run through registry; disabling removes from panel. _Shipped: `core/extensions/{types,registry,builtins}.ts` (dependency-free singleton, enablement as a disabled set, `subscribe`) + `src/extensions/mt` wrappers; `core/mt` `enabledProviders` + `useMTSettings` AND the user setting with the registry (reactive via `useSyncExternalStore`); disabled set persisted under the device-local `extensions.disabled` key, registered from `main.tsx`. No Add-ons page yet (6.2)._ +- **6.2 QA rules + formats as addons + Add-ons page — M — deps: 6.1.** Wrap `QA_CODES` rules and XLIFF/TMX/TBX/CSV/DOCX-export as contributions; `features/addons/AddonsPage.tsx` (catalogue, enable/disable, permission display, "suggested/built-in" UX); `/addons` route + nav. QA falls back to all-on when registry empty (keeps tests). **DoD**: Add-ons page lists everything pluggable; toggling a QA addon changes QA output. _Shipped: QA (`qa.`) + format (`format.*`) manifests in `core/extensions/builtins`; `core/qa/registryRules` (`effectiveQaRules` ANDs toggles with the registry, defaults on when unregistered) wired into `QAPanel` (reactive); `features/addons/AddonsPage` at `/addons` (+ nav) grouping addons by kind with permission chips + built-in badges — MT + QA toggle (enforced), formats shown Always-on (format-level gating deferred)._ - **6.3 Google Drive connector — M — deps: 6.1, 2.4.** `src/extensions/connectors/gdrive/` (Google Identity Services token client, `drive.file` scope — avoids verification burden; REST list/download/upload; token in memory/session only), generic `ConnectorFilePicker`; "From cloud" in ImportDialog + "Save to cloud" on DOCX export. Pure client OAuth — works for local-only users too. **DoD**: import DOCX from Drive; export back to Drive. -- **6.4 OneDrive connector — S — deps: 6.3.** `src/extensions/connectors/onedrive/` via dynamic `@azure/msal-browser` (`loginPopup` — avoids redirect/hash-router interplay), Graph `Files.ReadWrite`. **DoD**: same loop on OneDrive; both connectors appear as addons with permission labels. *Milestone 6 = extensions + connectors shipped.* +- **6.4 OneDrive connector — S — deps: 6.3.** `src/extensions/connectors/onedrive/` via dynamic `@azure/msal-browser` (`loginPopup` — avoids redirect/hash-router interplay), Graph `Files.ReadWrite`. **DoD**: same loop on OneDrive; both connectors appear as addons with permission labels. _Milestone 6 = extensions + connectors shipped._ ### Roadmap tail (not scheduled) + Tauri desktop (real mDNS LAN transport, native FS, iCloud via CloudKit), passkeys (native Supabase when available, else Edge-Function WebAuthn), marketplace + sandboxed third-party extensions (iframe/worker RPC host consuming the 6.1 manifest), project branches/merge atop `versions`, `@lexical/yjs` character-level same-segment co-editing (replacing lease+LWW), optional E2E encryption for cloud projects, Edge-Function compaction + server-side workflow validation, comments notifications. ## 5. Build automation: prompts + Routines (Pro-calibrated) ### 5.1 Mechanism + - **The repo is the coordination point.** [`STATUS.md`](./STATUS.md) tracks one line per phase (`pending / in-progress / in-review / done (PR #n)`); [`prompts/phase-N-N.md`](./prompts/) holds the per-phase prompt. Every autonomous session reads STATUS first — sessions are stateless and order-safe. - **One daily Routine, not 20+ triggers.** A single Routine (fresh session per fire) runs [`prompts/phase-runner.md`](./prompts/phase-runner.md): read STATUS → if the previous phase's PR is open, check CI/review comments, fix if needed, stop → else pick the next `pending` phase whose deps are `done`, load its prompt file, implement exactly that scope, run tests, push `claude/revamp-phase-N-N`, open draft PR, update STATUS, subscribe to PR activity. Robust to slipping: nothing double-fires; a skipped day costs nothing. - **The owner is the merge gate.** Each phase = one draft PR reviewed/merged by the owner; the next session only proceeds past a merged (or explicitly skipped) predecessor. - **Pro-sizing rules baked into every prompt**: one coherent slice; no drive-by refactors; on scope overflow, ship the core slice green and append the remainder to STATUS as phase N.N.1. Never leave the branch red. ### 5.2 Schedule + - Routine: daily at **10:00 UTC (07:00 América/São_Paulo — adjustable)**, fresh session per fire, push + email completion notifications on. - Expected calendar: ~4–5 weeks for Milestones 0–5 (web v2 launch candidate), +1–2 weeks for Milestone 6, at ~1 merged phase/day with occasional review-fix days. Phases 1.5, 2.x and 6.1 can interleave when a cloud phase is blocked on review. - Manual override: paste any `prompts/phase-N-N.md` into a fresh session whenever spare quota exists; STATUS keeps everything consistent. diff --git a/docs/revamp/STATUS.md b/docs/revamp/STATUS.md index 3ca9f98..8321765 100644 --- a/docs/revamp/STATUS.md +++ b/docs/revamp/STATUS.md @@ -4,42 +4,43 @@ Single source of truth for build progress. **Every revamp PR must update this fi States: `pending` · `in-progress` · `in-review (PR #n)` · `done (PR #n)` · `skipped (reason)`. Rules for autonomous sessions: + 1. Work on exactly one phase per session, chosen as: the first `pending` phase (top-to-bottom) whose **Depends on** entries are all `done`. 2. If a phase is `in-review`, check its PR for CI failures or review comments and fix them instead of starting a new phase. 3. Never mark a phase `done` yourself — only the owner merging the PR does that (the next session records the merge here). 4. Scope overflow: ship the core slice green, then append the remainder as a new row (e.g. `1.3.1`) here and in ROADMAP §4. -| Phase | Name | Size | Depends on | Status | -| --- | --- | --- | --- | --- | -| 0 | Bootstrap: roadmap, status, prompts, vision docs in repo | S | — | done (PR #37) | -| 0.1 | CI hardening (typecheck + vitest + Playwright on PRs) | S | — | done (PR #37) | -| 1.1 | Change model core + ChangeMarkNode + derivation semantics | M | 0.1 | done (PR #38) | -| 1.2 | Rich editing default-on + Playwright migration | M | 1.1 | done (PR #39) | -| 1.3 | Suggesting mode (edits become tracked suggestions) | M | 1.2 | done (PR #40) | -| 1.4 | Accept/reject + Changes panel rework | M | 1.3 | done (PR #41) | -| 1.5 | Range-anchored threaded comments | M | 1.2 | done (PR #42) | -| 1.6 | Review polish: confirm gating, navigation, attribution | S | 1.4, 1.5 | done (PR #43) | -| 2.1 | Document/block schema (Dexie v6) + backfill | M | 0.1 | done (PR #44) | -| 2.2 | DOCX import fidelity upgrade | M | 2.1 | done (PR #45) | -| 2.3 | Document preview pane | M | 2.1 | done (PR #46) | -| 2.4 | Clean DOCX export | M | 2.2, 2.3 | done (PR #47) | -| 3.1 | Supabase bootstrap + Google/magic-link auth + PKCE | M | 0.1 | done (PR #48) | -| 3.2 | Microsoft + Apple providers + account settings | S | 3.1 | done (PR #50) | -| 3.3 | Synced preferences/settings/layout | M | 3.1 | done (PR #51) | -| 3.3.1 | Sidebar layout sync (deferred from 3.3) | S | 3.3 | done (PR #53) | -| 3.4 | Personal term bank + TM sync | M | 3.3 | done (PR #52) | -| 4.1 | Cloud project schema + RLS + publish/join | M | 3.1 | done (PR #55) | -| 4.2 | SupabaseRealtimeTransport + chunking | M | 4.1 | done (PR #56) | -| 4.3 | Postgres persistence loop (catch-up, append, compaction) | M | 4.2 | done (PR #57) | -| 4.4 | Live collab UX: cursors, leases, attribution | M | 4.3, 1.6 | done (PR #58) | -| 4.4.1 | Remote caret overlay (live cursors, deferred from 4.4) | S | 4.4 | done (PR #59) | -| 5.1 | Members & roles management | M | 4.1 | done (PR #60) | -| 5.2 | Role-gated editing workflow | M | 5.1, 1.6 | done (PR #61) | -| 5.3 | Approval workflow + attribution in versioning | S | 5.2 | done (PR #62) | -| 6.1 | Extension registry + MT providers as built-in addons | M | 0.1 | done (PR #63) | -| 6.2 | QA rules + formats as addons + Add-ons page | M | 6.1 | done (PR #64) | -| 6.3 | Google Drive connector | M | 6.1, 2.4 | done (PR #65) | -| 6.4 | OneDrive connector | S | 6.3 | in-review (PR #66) | +| Phase | Name | Size | Depends on | Status | +| ----- | --------------------------------------------------------- | ---- | ---------- | ------------------ | +| 0 | Bootstrap: roadmap, status, prompts, vision docs in repo | S | — | done (PR #37) | +| 0.1 | CI hardening (typecheck + vitest + Playwright on PRs) | S | — | done (PR #37) | +| 1.1 | Change model core + ChangeMarkNode + derivation semantics | M | 0.1 | done (PR #38) | +| 1.2 | Rich editing default-on + Playwright migration | M | 1.1 | done (PR #39) | +| 1.3 | Suggesting mode (edits become tracked suggestions) | M | 1.2 | done (PR #40) | +| 1.4 | Accept/reject + Changes panel rework | M | 1.3 | done (PR #41) | +| 1.5 | Range-anchored threaded comments | M | 1.2 | done (PR #42) | +| 1.6 | Review polish: confirm gating, navigation, attribution | S | 1.4, 1.5 | done (PR #43) | +| 2.1 | Document/block schema (Dexie v6) + backfill | M | 0.1 | done (PR #44) | +| 2.2 | DOCX import fidelity upgrade | M | 2.1 | done (PR #45) | +| 2.3 | Document preview pane | M | 2.1 | done (PR #46) | +| 2.4 | Clean DOCX export | M | 2.2, 2.3 | done (PR #47) | +| 3.1 | Supabase bootstrap + Google/magic-link auth + PKCE | M | 0.1 | done (PR #48) | +| 3.2 | Microsoft + Apple providers + account settings | S | 3.1 | done (PR #50) | +| 3.3 | Synced preferences/settings/layout | M | 3.1 | done (PR #51) | +| 3.3.1 | Sidebar layout sync (deferred from 3.3) | S | 3.3 | done (PR #53) | +| 3.4 | Personal term bank + TM sync | M | 3.3 | done (PR #52) | +| 4.1 | Cloud project schema + RLS + publish/join | M | 3.1 | done (PR #55) | +| 4.2 | SupabaseRealtimeTransport + chunking | M | 4.1 | done (PR #56) | +| 4.3 | Postgres persistence loop (catch-up, append, compaction) | M | 4.2 | done (PR #57) | +| 4.4 | Live collab UX: cursors, leases, attribution | M | 4.3, 1.6 | done (PR #58) | +| 4.4.1 | Remote caret overlay (live cursors, deferred from 4.4) | S | 4.4 | done (PR #59) | +| 5.1 | Members & roles management | M | 4.1 | done (PR #60) | +| 5.2 | Role-gated editing workflow | M | 5.1, 1.6 | done (PR #61) | +| 5.3 | Approval workflow + attribution in versioning | S | 5.2 | done (PR #62) | +| 6.1 | Extension registry + MT providers as built-in addons | M | 0.1 | done (PR #63) | +| 6.2 | QA rules + formats as addons + Add-ons page | M | 6.1 | done (PR #64) | +| 6.3 | Google Drive connector | M | 6.1, 2.4 | done (PR #65) | +| 6.4 | OneDrive connector | S | 6.3 | in-review (PR #66) | ## Log @@ -63,7 +64,7 @@ Rules for autonomous sessions: - 2026-07-19 — Phase 2.2 merged in PR #45. Phase 2.3 built: live document preview. `core/documents/render.ts` — pure `renderDocument(blocks, segments)` assembling the block tree with each block's translated segment targets (ordered by `sentenceIndex`, source fallback + tint for untranslated), table cells mapped to segments row-major, image asset ids carried; `RenderBlock`/`RenderCell`/`RenderSegmentRef` model. `preview/usePreviewStore.ts` (open + mode: source/target/split) and `preview/DocumentPreview.tsx` (renders blocks with formatting runs on the source side, plain translated text on the target side, click-a-block → `jumpToSegment`). EditorPage shows a **Preview** toggle in the header row and the pane above the segment list, gated on the project having a document (monolingual only; XLIFF hidden). Live via `useLiveQuery`. Deviation (noted): the toggle lives in the EditorPage header row (beside Stage/EditMode) rather than the stage-scoped EditorToolbar, so it's available in every stage. Tests: `documents.render` (order, target/source fallback, sentence-order, table→segment mapping + image asset), `document-preview.spec.ts` e2e (toggle → edit → live update → click-to-focus → source mode). Green: 542 unit (+3) / 26 e2e (+1) / build; e2e stable twice. - 2026-07-19 — Phase 2.3 merged in PR #46. Phase 2.4 built: clean DOCX export (**Milestone 2 complete**). Added `docx` (runtime dep, **dynamic-import only** — split into its own ~400KB chunks, zero initial-bundle cost) + `jszip` (devDep, for tests). `core/documents/targetRuns.ts` — pure `targetRichToRuns` decodes Lexical text-format flags into `InlineRun[]` with D2 accepted-preview (insertions kept, deletions dropped), hyperlink href, inline-tag placeholders. `core/documents/toDocx.ts` — `exportProjectDocx(blocks, segments, assets)` reconstructs a Document via `import('docx')`: headings (levels), lists (bullet + decimal numbering), blockquotes (indent), code (monospace), tables (translated cells), images (ImageRun from asset blobs); target formatting from `targetRich` runs else plain, source fallback for untranslated. `hasPendingForExport` flags unresolved changes. `tools/ExportDocxButton.tsx` (header row, gated on a document; downloads via object URL; warning marker when pending changes exist). Tests: `documents.targetRuns` (format flags, accepted-preview, links/tags), `documents.toDocx` (unzip document.xml via jszip -> assert translated text + Heading1 style + bold + table + source fallback). Green: 550 unit (+8) / 27 e2e (+1: export download) / build; docx confirmed as a separate lazy chunk; e2e stable twice. - **Milestone 2 complete** (pending PR #47 merge): document/block model, DOCX import fidelity (runs/tables/images), live preview, and clean DOCX export — the full Word round-trip in -> translate -> out. -- 2026-07-19 — Phase 2.4 merged in PR #47 (**Milestone 2 complete**). Phase 3.1 built: Supabase bootstrap + Google/magic-link auth (start of Milestone 3, accounts). Strictly additive behind `VITE_SUPABASE_URL`/`VITE_SUPABASE_ANON_KEY` (D6): `storage/cloud/supabaseClient.ts` (env-gated lazy singleton; `import type { SupabaseClient }` erased at build; value lib loaded only via dynamic `import('@supabase/supabase-js')` on first use; `flowType:'pkce'` + `detectSessionInUrl:false` pinned; `configuredProviders()` from `VITE_AUTH_PROVIDERS`, default `['google']`). `storage/cloud/authBootstrap.ts` (D7): pure `stripAuthQuery`/`hasAuthRedirect` + `maybeHandleAuthRedirect()` — exchanges the PKCE `?code=` (which lands *before* the `#/route` fragment) and `history.replaceState`s the query away before the hash router mounts; a no-op that loads no Supabase code when unconfigured. `features/account/`: `useAuthStore` (zustand; `status` unconfigured/loading/authenticated/unauthenticated, `init()` gets session + subscribes `onAuthStateChange` once, `signInWithOAuth`/`signInWithMagicLink`/`signOut`; redirects to `origin+pathname`, never the hash), `SignInDialog` (configured OAuth buttons + magic-link email + sent state), `AccountMenu` (TopBar control, renders `null` unless cloud-configured). `main.tsx` awaits the redirect bootstrap before render; `TopBar` mounts `AccountMenu`. `supabase/migrations/0001_profiles.sql` (profiles + owner-scoped RLS + signup trigger seeding display name/avatar from provider metadata). `docs/cloud.md` (env vars, project + provider setup, exact redirect-URL allow-list incl. `http://localhost:5173/`, honest passkeys-are-fast-follow note, manual test matrix). CI gains a second `pnpm build` with dummy cloud env so the signed-in code path can't regress unnoticed. Verified: flag OFF → **zero** Supabase code in the bundle (grep for `GoTrueClient`/`SupabaseClient` empty); flag ON → Supabase isolated to a lazy chunk, absent from the entry chunk. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-3-1`; broader profile-read RLS for invite-by-email deferred to 5.1 (least-privilege base install). Tests: `cloud.authBootstrap` (query-strip incl. `?code=x#/project/y`, redirect detection, unconfigured no-op leaves history untouched), `cloud.supabaseClient` (flag-off rejects before importing the lib, provider parsing, flag-on via `resetModules`+`stubEnv`). Green: 564 unit (+14) / 27 e2e / build (both flag-off and flag-on); e2e stable twice. +- 2026-07-19 — Phase 2.4 merged in PR #47 (**Milestone 2 complete**). Phase 3.1 built: Supabase bootstrap + Google/magic-link auth (start of Milestone 3, accounts). Strictly additive behind `VITE_SUPABASE_URL`/`VITE_SUPABASE_ANON_KEY` (D6): `storage/cloud/supabaseClient.ts` (env-gated lazy singleton; `import type { SupabaseClient }` erased at build; value lib loaded only via dynamic `import('@supabase/supabase-js')` on first use; `flowType:'pkce'` + `detectSessionInUrl:false` pinned; `configuredProviders()` from `VITE_AUTH_PROVIDERS`, default `['google']`). `storage/cloud/authBootstrap.ts` (D7): pure `stripAuthQuery`/`hasAuthRedirect` + `maybeHandleAuthRedirect()` — exchanges the PKCE `?code=` (which lands _before_ the `#/route` fragment) and `history.replaceState`s the query away before the hash router mounts; a no-op that loads no Supabase code when unconfigured. `features/account/`: `useAuthStore` (zustand; `status` unconfigured/loading/authenticated/unauthenticated, `init()` gets session + subscribes `onAuthStateChange` once, `signInWithOAuth`/`signInWithMagicLink`/`signOut`; redirects to `origin+pathname`, never the hash), `SignInDialog` (configured OAuth buttons + magic-link email + sent state), `AccountMenu` (TopBar control, renders `null` unless cloud-configured). `main.tsx` awaits the redirect bootstrap before render; `TopBar` mounts `AccountMenu`. `supabase/migrations/0001_profiles.sql` (profiles + owner-scoped RLS + signup trigger seeding display name/avatar from provider metadata). `docs/cloud.md` (env vars, project + provider setup, exact redirect-URL allow-list incl. `http://localhost:5173/`, honest passkeys-are-fast-follow note, manual test matrix). CI gains a second `pnpm build` with dummy cloud env so the signed-in code path can't regress unnoticed. Verified: flag OFF → **zero** Supabase code in the bundle (grep for `GoTrueClient`/`SupabaseClient` empty); flag ON → Supabase isolated to a lazy chunk, absent from the entry chunk. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-3-1`; broader profile-read RLS for invite-by-email deferred to 5.1 (least-privilege base install). Tests: `cloud.authBootstrap` (query-strip incl. `?code=x#/project/y`, redirect detection, unconfigured no-op leaves history untouched), `cloud.supabaseClient` (flag-off rejects before importing the lib, provider parsing, flag-on via `resetModules`+`stubEnv`). Green: 564 unit (+14) / 27 e2e / build (both flag-off and flag-on); e2e stable twice. - 2026-07-19 — Cloud infra provisioned (human created the Supabase project `qutcuzlppbjbsymowavc`, us-east-2, PG17). Migrations `0001_profiles` + `0002_profiles_hardening` applied via the Supabase MCP; security advisor clean (no lints). Added `0002_profiles_hardening.sql` (pins trigger `search_path`, revokes RPC EXECUTE on the trigger-only functions), `.env.example` (real URL + publishable key — public by design; secret key never committed), and wired `deploy.yml` to inject `VITE_SUPABASE_URL/ANON_KEY/AUTH_PROVIDERS` from repo Variables (unset ⇒ still local-only). `docs/cloud.md` updated with this project's concrete values. Remaining human steps: add the repo Variables for production, set Supabase Site URL + redirect allow-list (`https://verbalis.britx.me/`, `http://localhost:5173/`), configure the Google provider, and rotate the secret key (it was shared in chat). No app source changed — infra/docs only. - 2026-07-19 — Phase 3.1 merged in PR #48 (STATUS was stale — recorded here). Phase 3.2 built: Microsoft + Apple providers + account settings. Provider buttons were already env-driven from 3.1 (`configuredProviders()` → `SignInDialog` filters to the natively-supported `google`/`azure`/`apple`); this phase adds recognisable brand marks (`features/account/ProviderIcon.tsx` — multi-colour Google "G", four-square Microsoft, monochrome Apple glyph that inherits button colour; unknown providers get no glyph) so the three "Continue with …" buttons are visually distinct. Unconfigured providers stay hidden (DoD). New `features/account/AccountSettingsSection.tsx` — the signed-in counterpart to the local `ProfileSettingsSection`, surfaced as a **Settings → Account** section that only appears when the cloud is configured (`visibleNav()` filters it in `settings/index.tsx`; `UserCog` icon). Signed out it offers a Sign in button (reuses `SignInDialog`); signed in it shows: an editable **display name** persisted to the `profiles` row via an explicit Save (dirty-gated, "Saved" confirmation), **linked sign-in methods** (Google/Microsoft/Apple/Email), and **Sign out**. `useAuthStore` gains `profileDisplayName`/`identities` state + `loadAccount()` (parallel `profiles` select + `supabase.auth.getUserIdentities()`, prefers the account name in the top-bar menu) and `updateDisplayName()` (upsert to `profiles`, trims → null when cleared, patches the menu user live, surfaces RLS errors); both reset on sign-out/session-end. Strictly additive: the new statically-imported components read only the env-gated `isCloudConfigured()`/`configuredProviders()` — no static `@supabase/supabase-js` (verified: flag-off `dist` has zero `GoTrueClient`/`SupabaseAuthClient`; the lib stays behind `getSupabase()`'s dynamic import). Docs: `docs/cloud.md` §7 manual-verification block (Azure app registration + Apple Developer Program requirement already in §4). Deviation (noted): built on the designated session branch, not `claude/revamp-phase-3-2`. Tests: `account.store` (loadAccount name+identity mapping and provider-name fallback; updateDisplayName trim/clear/error via an injected mock client), `account.settings` (unconfigured/signed-out/authenticated renders + save-through-store + Account nav hidden when unconfigured). Green: 574 unit (+10) / 27 e2e / build (flag-off bundle Supabase-free); no new e2e (ROADMAP §4 asks none for 3.2). - 2026-07-19 — Phase 3.2 merged in PR #50. Phase 3.3 built: cloud settings sync. `supabase/migrations/0003_user_settings.sql` (`user_settings(user_id,key,value jsonb,updated_at)`, PK `(user_id,key)`, owner-scoped RLS for select/insert/update/delete; `updated_at` is **client-supplied** so per-key LWW is meaningful across devices — no touch-trigger). Numbering deviation: ROADMAP §3.3 sketched `0002_user_settings.sql`, but `0002` was taken by `0002_profiles_hardening.sql` (a 3.1 follow-up), so it shipped as `0003`; §3.4's personal-resources migration shifts to `0004` (noted in ROADMAP). `src/storage/cloud/settingsSync.ts`: `SYNCED_SETTINGS_KEYS` allowlist = `editor.prefs` / `lookup.defaults` / `spell.dicts` only — **MT `mt.providers` excluded (API keys never sync)**, and so are web-search providers (keys), `profile.identity` (device-local author id), and semantic-TM (device model). Pure `reconcileSettings(local, remote)` does per-key LWW (newer `updatedAt` wins; present-on-one-side flows to the other; equal = untouched; non-allowlisted ignored). `pullAndReconcile(client, userId)` + `pushKey(client, userId, key)` take an **injected** Supabase client (vitest never hits the network). Wiring: `startSettingsSync()` subscribes to `useAuthStore` (pull once per authenticated user id — token refreshes don't re-pull) and to a new settings-change fan-out, debouncing pushes (500ms); `stopSettingsSync()` tears down. `settingsRepo` gained `updatedAt` stamping on `set`, a silent `applyRemote(key,value,updatedAt)` for pulls (no echo), `getRow`, and `subscribeSettingsChange`; `SettingsRow.updatedAt?` added to `db.ts` (unindexed — no Dexie version bump; pre-3.3 rows read as "oldest"). `main.tsx` starts the reconciler via a guarded **dynamic** `import('./storage/cloud/settingsSync')` only when `isCloudConfigured()`. Dexie stays the read path — settings sections use `useLiveQuery`, so a pull's `applyRemote` repaints the UI. Verified: flag-OFF `dist` has **zero** Supabase code and no `user_settings`/`SYNCED_SETTINGS` (settingsSync tree-shaken); flag-ON keeps `GoTrueClient` out of the entry chunk and puts `settingsSync` + `user_settings` in their own lazy chunks. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-3-3`; **sidebar layout sync deferred to Phase 3.3.1** (appended to ROADMAP §4 + the table) — the layout lives in a zustand/localStorage `persist` store with no LWW timestamps or Dexie read path, so folding it into the reconciler (per-mutation timestamps + post-pull rehydration) is its own slice; the engine is already generic, so 3.3.1 is just an adapter. `docs/cloud.md` (§3 migration list + §8 manual verification). Tests: `cloud.settingsSync` (allowlist excludes MT; reconciler push/pull/newer-wins/equal/non-allowlisted; `pullAndReconcile` applies remote-wins + pushes local-wins against real fake-indexeddb Dexie with a mock client; `pushKey` allowlist gating + absent no-op; `readLocalSyncedEntries`), `cloud.settingsSync.wiring` (pull-on-signin lands in Dexie, debounced push-on-change while signed in, no push when signed out). Green: 588 unit (+14) / 27 e2e / build (flag-off Supabase-free, flag-on lazy); no new e2e (ROADMAP §4 asks none). @@ -71,14 +72,14 @@ Rules for autonomous sessions: - **Milestone 3 complete** (pending PR merge): accounts — Supabase auth (Google/Microsoft/Apple/magic-link), account settings, synced preferences, and personal term-bank + TM sync, all strictly additive behind `VITE_SUPABASE_URL`. - 2026-07-20 — Phase 3.4 merged in PR #52 (**Milestone 3 complete**). Phase 3.3.1 built: sidebar layout sync (the layout deferred from 3.3). `useSidebarPanelStore` gains a persisted LWW `updatedAt` stamped on every layout/collapsed mutation (`setTab`/`setLayout`/`showPanel`/`togglePanel`/`toggleCollapsed`/`movePanel`/`resetLayout`; transient open/active-tab never bump it) + `partialize`/`merge` carry it; `defaultLayout` exported for rehydration backfill. New `features/editor/layoutSync.ts` syncs the layout through the same `user_settings` cloud table under the `sidebar.layout` key with the same client-supplied LWW: `syncLayout(client, userId)` (pull-newer applies to the live store via `setState` — rehydrating machine B without a reload, backfilling any stage a stale remote misses — else push local-newer), plus `startLayoutSync()`/`stopLayoutSync()` wiring (reconcile once per new sign-in; debounced push on store change, guarded by an `applyingRemote` flag so a pull's `setState` never echoes back as a push). `main.tsx` starts it via a guarded **dynamic** import only when `isCloudConfigured()`. Deviation (noted in ROADMAP §4): the 3.3 `settingsSync` engine is bound to Dexie `settingsRepo` keys (not a source-agnostic adapter engine as the 3.3.1 note optimistically implied), so the layout — a zustand/localStorage store — syncs as a small **sibling** reconciler reusing `user_settings` + LWW rather than a settingsSync "adapter". No 3.3 code refactored (keeps that phase's tests untouched). `settingsSync` never sees the `sidebar.layout` row (its `.in()` query is scoped to `SYNCED_SETTINGS_KEYS`). Verified: flag-OFF `dist` has **zero** `GoTrueClient`/`syncLayout`/`user_settings` (layoutSync tree-shaken; the only `sidebar.layout` substring is the store's `verbalis.sidebar.layout` localStorage key — local code); flag-ON keeps `GoTrueClient` out of the entry and puts `layoutSync` in its own lazy chunk. Built on the designated session branch, not `claude/revamp-phase-3-3-1`. `docs/cloud.md` §8.1 verification. Tests: `layoutSync` (LWW: apply-newer-remote rehydrates the store, push-local-newer, push-when-no-remote-row, equal-noop), `layoutSync.wiring` (sign-in applies the cloud layout live; debounced push on a local layout change). Green: 608 unit (+6) / 27 e2e / build (flag-off layout-sync-free, flag-on lazy). - 2026-07-20 — Phase 3.3.1 merged in PR #53. Phase 4.1 built: cloud project schema + RLS + publish/open (start of **Milestone 4**, real-time collaboration). Migrations applied to the live project via the Supabase MCP (advisor clean bar the unrelated leaked-password Auth toggle): `0005_projects.sql` — `projects`, `project_members(role project_role enum: project_manager|translator|revisor)`, `ydoc_state(state bytea, seq)`, append-only `ydoc_updates(update bytea, author_id default auth.uid())` (tamper-evident attribution, D8), member-scoped RLS via `is_project_member`/`has_project_role` helpers, a private `project-files` storage bucket. `0006_projects_helpers_private.sql` — moves the two SECURITY DEFINER membership helpers into a non-API `private` schema (repoints every 0005 policy) so they stop being RPC-exposed while RLS keeps calling them, clearing the linter's 0029 lints. Numbering deviation: ROADMAP §4.1 sketched `0004_projects.sql`, but `0003`/`0004` were taken by the 3.3/3.4 migrations, so this shipped as `0005` + a `0006` hardening follow-up. `src/core/types`: `ProjectRole` + additive `Project.cloud?: {id, role}` (unindexed; local-only projects never carry it). `src/storage/cloud/bytea.ts`: pure hex-literal encode/decode for bytea-over-PostgREST (base64 decode fallback). `src/storage/cloud/projectCloud.ts`: pure client-injected data layer (`insertCloudProject` seeds the project row + owner `project_manager` membership + `ydoc_state` snapshot; `listCloudProjects`; `fetchCloudProject` → summary+role; `fetchYdocState` → decoded bytes) + orchestration (`publishProject` encodes the project's live Yjs doc via `acquireProjectDoc`+`Y.encodeStateAsUpdate`, inserts, stamps `project.cloud`; `openCloudProject` fetches metadata+snapshot, hydrates a fresh local Dexie project + its segments via `readAllSegments`, reusing an existing local copy rather than duplicating). Strictly additive — `getSupabase()` dynamic, gated on `isCloudConfigured()`. UI (self-gating, cloud logic dynamically imported so the projects route stays lean): `features/projects/cloud/CloudControls.tsx` (`CloudBadge`, owner `PublishCloudButton` on a card, `OpenFromCloudButton` header action — all render null unless configured + signed-in) + `OpenCloudProjectDialog.tsx` (lists member projects, opens → navigates to the local copy); wired into `ProjectCard` + projects `index.tsx`. Verified: flag-OFF `dist` has **zero** `GoTrueClient`; flag-ON keeps Supabase in a lazy chunk out of the entry. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-4-1`; the two-account publish→open→RLS-deny round-trip is the manual DoD (needs two real sessions; vitest uses an injected mock client). Tests: `cloud.bytea` (hex round-trip incl. all 256 byte values + empty + base64 fallback), `cloud.projectCloud` (insert creates all three rows with the owner as project_manager and a decodable snapshot; list camelCase mapping; fetch summary+role incl. translator default + null-when-absent; error propagation — all via an in-memory client mirroring the PostgREST chains). Green: 618 unit (+10) / 27 e2e twice / build (flag-off Supabase-free, flag-on lazy); no new e2e (ROADMAP §4 asks none for 4.1). -- 2026-07-20 — Phase 4.1 merged in PR #55. Phase 4.2 built: `SupabaseRealtimeTransport` + chunking (pure client, no schema change). `src/storage/sync/transport/chunking.ts` — the protocol core: base64 wire encoding (`bytesToBase64`/`base64ToBytes`), `encodeMessage(SyncMessage, limit=60000)` frames a message into `WireChunk`s (Realtime is JSON + ~250KB/msg, so binary payloads are base64 +33% and split), and an order-independent `ChunkReassembler` that yields a `SyncMessage` once all chunks of a message id arrive (single-chunk pass-through; payloadless kinds `hello`/`bye`/`state-request` carry `d:''`). `src/storage/sync/transport/supabaseRealtime.ts` — `SupabaseRealtimeTransport implements SyncTransport` over a private Realtime broadcast channel `project:{cloudId}` (`broadcast:{self:false}`, so no self-echo, matching BroadcastChannel); `getSupabase()` is dynamic so no eager supabase-js. Outbound `update`s are coalesced by `OutboundUpdateBatcher` (debounce 300ms → `Y.mergeUpdates` → one broadcast); other kinds send immediately; sends before `SUBSCRIBED` buffer in an outbox flushed on subscribe. The channel is injectable (`channelProvider`) as a test seam. `createTransport(projectId, {cloudId?})` gains a cloud branch (Supabase when `cloudId` set; **BroadcastChannel/Tauri paths byte-identical otherwise**); `syncManager.startProjectSync` computes `cloudId` from `project.cloud` when signed-in + `isCloudConfigured()`, else `undefined`. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-4-2`; the transport is complete + unit-covered now, but the end-to-end **two-browser live edit** DoD needs the Postgres initial-state loop (4.3) + the auto-start-on-open UI (4.4) — the transport is selectable but not yet auto-wired for cloud projects (that's 4.4), stated honestly. No migration (4.2 is transport-only). Tests: `sync.chunking` (base64 all-bytes/empty round-trip; single + multi-chunk encode/reassemble; out-of-order arrival; two interleaved messages stay separate; payloadless pass-through), `sync.realtimeTransport` (`OutboundUpdateBatcher` merges a burst into one flush that replays both edits via fake timers + no-fire-after-destroy; transport delivers a 50KB multi-chunk `state` to the *other* peer only via a linked in-memory channel pair — caught + fixed a real ordering bug where the outbox flushed before `this.channel` was assigned; pre-subscribe sends buffer then flush). Green: 629 unit (+11) / 27 e2e twice / build (flag-off has zero `GoTrueClient`; flag-on keeps Supabase a lazy chunk out of the entry). -- 2026-07-20 — Phase 4.2 merged in PR #56 (STATUS was stale — recorded here). Phase 4.3 built: the Postgres persistence loop (Realtime is latency, Postgres is truth). `supabase/migrations/0007_compaction.sql` — the `claim_compaction(project_id, expected_seq, state, up_to_id)` RPC (SECURITY DEFINER, membership-checked): atomically bumps the `ydoc_state.seq` generation **only while it still matches** `expected_seq`, installs the new compacted snapshot, and prunes `ydoc_updates` with `id <= up_to_id`. Numbering deviation: ROADMAP §4.3 sketched `0005_compaction.sql`, but `0005`/`0006` were taken by the 4.1 project migrations, so it ships as `0007`; and the RPC signature is **widened** from the sketch's `(project_id, expected_seq)` to also carry the new snapshot + pruned-through id, so the whole compaction is one atomic RLS-safe transaction (the append log has no DELETE policy by design — D8 tamper-evidence — so pruning goes through this controlled definer path, membership re-checked inside). `src/storage/cloud/ydocPersistence.ts` — pure client-injected data layer (`fetchSnapshot`/`fetchUpdates`/`appendUpdate`/`countUpdates`/`claimCompaction`, hex-bytea via 4.1's `bytea.ts`) + the `createYdocPersistence({cloudId, doc, client})` loop: **catch-up** fetches `ydoc_state` + the whole `ydoc_updates` log, merges (`Y.mergeUpdates`) and applies as `ORIGIN_REMOTE` (so neither the transport nor this loop re-broadcasts/re-appends it), then pushes anything the local doc holds that the cloud lacks (offline edits / pre-publish history) by diffing state vectors (`Y.encodeStateAsUpdate(doc, remoteSV)` when `localAheadOf`); **append** buffers local `doc.on('update')` deltas (ignoring `ORIGIN_REMOTE`), debounce-merges them (500ms) into one attributed `ydoc_updates` row, and re-buffers/retries on a failed (offline) append; **compaction** triggers once the log passes 200 rows — it re-reads the *authoritative* snapshot+updates from Postgres (never the possibly-lagging local doc) to build the new snapshot, so it subsumes exactly the rows it prunes, then optimistically `claim_compaction`s (loser gets `null` and no-ops — the log is already bounded). Catch-up is re-runnable (idempotent Yjs replay), which is what converges an offline member on reconnect. Wiring: `syncManager.startProjectSync` starts the loop (dynamic `startYdocPersistence`, only when `cloudId` set + signed-in + `isCloudConfigured()`) alongside the realtime transport, kicks off a non-blocking `catchUp()`, stores the handle on the ref-counted session, and destroys it in `stopProjectSync`; local-only projects never touch it. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-4-3`; the RPC signature widening above; live **reconnect re-sync** (re-`hello`/re-`catchUp` when Realtime drops-then-resubscribes) is left to 4.4, which owns the transport/auto-start UX — 4.3 converges on **reopen** (each open runs catch-up), the DoD proven at the persistence layer. `docs/cloud.md` (§3 migration list + §10 manual verification). Tests: `cloud.ydocPersistence` (catch-up converges a fresh doc from snapshot+log; append coalesces a burst into one row; applied-remote is never re-appended; **offline-A/online-B converge through Postgres with no edit lost** — the DoD, on a shared in-memory server; compaction folds+prunes past the threshold and bumps seq, and a stale optimistic claim returns `null`; `fetchSnapshot`/`fetchUpdates` bytea round-trips). Green: 636 unit (+7) / build (flag-off: **zero** `GoTrueClient`/`claim_compaction` — the loop tree-shakes out entirely when the cloud is unconfigured; flag-on: `GoTrueClient` and the persistence loop each in their own lazy chunk, out of the entry). No new e2e (ROADMAP §4 asks none for 4.3; the two-member live DoD needs a real backend, per the §10 manual matrix). +- 2026-07-20 — Phase 4.1 merged in PR #55. Phase 4.2 built: `SupabaseRealtimeTransport` + chunking (pure client, no schema change). `src/storage/sync/transport/chunking.ts` — the protocol core: base64 wire encoding (`bytesToBase64`/`base64ToBytes`), `encodeMessage(SyncMessage, limit=60000)` frames a message into `WireChunk`s (Realtime is JSON + ~250KB/msg, so binary payloads are base64 +33% and split), and an order-independent `ChunkReassembler` that yields a `SyncMessage` once all chunks of a message id arrive (single-chunk pass-through; payloadless kinds `hello`/`bye`/`state-request` carry `d:''`). `src/storage/sync/transport/supabaseRealtime.ts` — `SupabaseRealtimeTransport implements SyncTransport` over a private Realtime broadcast channel `project:{cloudId}` (`broadcast:{self:false}`, so no self-echo, matching BroadcastChannel); `getSupabase()` is dynamic so no eager supabase-js. Outbound `update`s are coalesced by `OutboundUpdateBatcher` (debounce 300ms → `Y.mergeUpdates` → one broadcast); other kinds send immediately; sends before `SUBSCRIBED` buffer in an outbox flushed on subscribe. The channel is injectable (`channelProvider`) as a test seam. `createTransport(projectId, {cloudId?})` gains a cloud branch (Supabase when `cloudId` set; **BroadcastChannel/Tauri paths byte-identical otherwise**); `syncManager.startProjectSync` computes `cloudId` from `project.cloud` when signed-in + `isCloudConfigured()`, else `undefined`. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-4-2`; the transport is complete + unit-covered now, but the end-to-end **two-browser live edit** DoD needs the Postgres initial-state loop (4.3) + the auto-start-on-open UI (4.4) — the transport is selectable but not yet auto-wired for cloud projects (that's 4.4), stated honestly. No migration (4.2 is transport-only). Tests: `sync.chunking` (base64 all-bytes/empty round-trip; single + multi-chunk encode/reassemble; out-of-order arrival; two interleaved messages stay separate; payloadless pass-through), `sync.realtimeTransport` (`OutboundUpdateBatcher` merges a burst into one flush that replays both edits via fake timers + no-fire-after-destroy; transport delivers a 50KB multi-chunk `state` to the _other_ peer only via a linked in-memory channel pair — caught + fixed a real ordering bug where the outbox flushed before `this.channel` was assigned; pre-subscribe sends buffer then flush). Green: 629 unit (+11) / 27 e2e twice / build (flag-off has zero `GoTrueClient`; flag-on keeps Supabase a lazy chunk out of the entry). +- 2026-07-20 — Phase 4.2 merged in PR #56 (STATUS was stale — recorded here). Phase 4.3 built: the Postgres persistence loop (Realtime is latency, Postgres is truth). `supabase/migrations/0007_compaction.sql` — the `claim_compaction(project_id, expected_seq, state, up_to_id)` RPC (SECURITY DEFINER, membership-checked): atomically bumps the `ydoc_state.seq` generation **only while it still matches** `expected_seq`, installs the new compacted snapshot, and prunes `ydoc_updates` with `id <= up_to_id`. Numbering deviation: ROADMAP §4.3 sketched `0005_compaction.sql`, but `0005`/`0006` were taken by the 4.1 project migrations, so it ships as `0007`; and the RPC signature is **widened** from the sketch's `(project_id, expected_seq)` to also carry the new snapshot + pruned-through id, so the whole compaction is one atomic RLS-safe transaction (the append log has no DELETE policy by design — D8 tamper-evidence — so pruning goes through this controlled definer path, membership re-checked inside). `src/storage/cloud/ydocPersistence.ts` — pure client-injected data layer (`fetchSnapshot`/`fetchUpdates`/`appendUpdate`/`countUpdates`/`claimCompaction`, hex-bytea via 4.1's `bytea.ts`) + the `createYdocPersistence({cloudId, doc, client})` loop: **catch-up** fetches `ydoc_state` + the whole `ydoc_updates` log, merges (`Y.mergeUpdates`) and applies as `ORIGIN_REMOTE` (so neither the transport nor this loop re-broadcasts/re-appends it), then pushes anything the local doc holds that the cloud lacks (offline edits / pre-publish history) by diffing state vectors (`Y.encodeStateAsUpdate(doc, remoteSV)` when `localAheadOf`); **append** buffers local `doc.on('update')` deltas (ignoring `ORIGIN_REMOTE`), debounce-merges them (500ms) into one attributed `ydoc_updates` row, and re-buffers/retries on a failed (offline) append; **compaction** triggers once the log passes 200 rows — it re-reads the _authoritative_ snapshot+updates from Postgres (never the possibly-lagging local doc) to build the new snapshot, so it subsumes exactly the rows it prunes, then optimistically `claim_compaction`s (loser gets `null` and no-ops — the log is already bounded). Catch-up is re-runnable (idempotent Yjs replay), which is what converges an offline member on reconnect. Wiring: `syncManager.startProjectSync` starts the loop (dynamic `startYdocPersistence`, only when `cloudId` set + signed-in + `isCloudConfigured()`) alongside the realtime transport, kicks off a non-blocking `catchUp()`, stores the handle on the ref-counted session, and destroys it in `stopProjectSync`; local-only projects never touch it. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-4-3`; the RPC signature widening above; live **reconnect re-sync** (re-`hello`/re-`catchUp` when Realtime drops-then-resubscribes) is left to 4.4, which owns the transport/auto-start UX — 4.3 converges on **reopen** (each open runs catch-up), the DoD proven at the persistence layer. `docs/cloud.md` (§3 migration list + §10 manual verification). Tests: `cloud.ydocPersistence` (catch-up converges a fresh doc from snapshot+log; append coalesces a burst into one row; applied-remote is never re-appended; **offline-A/online-B converge through Postgres with no edit lost** — the DoD, on a shared in-memory server; compaction folds+prunes past the threshold and bumps seq, and a stale optimistic claim returns `null`; `fetchSnapshot`/`fetchUpdates` bytea round-trips). Green: 636 unit (+7) / build (flag-off: **zero** `GoTrueClient`/`claim_compaction` — the loop tree-shakes out entirely when the cloud is unconfigured; flag-on: `GoTrueClient` and the persistence loop each in their own lazy chunk, out of the entry). No new e2e (ROADMAP §4 asks none for 4.3; the two-member live DoD needs a real backend, per the §10 manual matrix). - 2026-07-20 — Phase 4.3 merged in PR #57. Phase 4.4 built: live-collab UX — per-segment edit leases, attribution, and the stale-LWW guard (transport-agnostic, so it works for local BroadcastChannel peers and cloud Realtime peers identically). `src/storage/sync/presence.ts` gains a stable `userId` (Supabase user id when signed in, else the device-local `authorId` — so attribution survives a peer reconnecting under a fresh peerId, D8) and a `CaretRange` on both `PresenceWire` and `PeerPresence`; `syncSession` broadcasts them (`setActiveSegment(id, caret?)`) and `syncManager` resolves `userId` via `ensureLocalAuthor()`/auth. `src/storage/sync/lease.ts` — pure `segmentLease(segmentId, selfPeerId, selfActiveSegmentId, peers)`: among everyone editing a segment the **lowest peerId owns** (D4), the rest are viewers; symmetric + deterministic so two peers entering at once always agree, and the lease **releases the moment the owner blurs** (its `activeSegmentId` moves off) and the next-lowest peer takes over. Wiring: `usePresenceStore` carries `selfPeerId`; `useProjectSync` publishes it; `EditorPage` computes `leaseHolderFor(segmentId)` and passes `leaseLockedBy` to each `SegmentRow`, which renders the target read-only (a separate `editLocked = locked || leaseLockedBy`, kept distinct from the persistent segment lock) with a coloured "{name} is editing" chip. Attribution "everyone's changes tracked" falls out of M1 + sync for free — remote suggesting-mode edits already arrive as pre-attributed `ChangeMarkNode`s. `reverseBridge.ts` stale-LWW guard (risk #5): `target` (char-level Y.Text) and `targetRich` are separate CRDT fields, so a concurrent merge can land a `targetRich` that no longer derives to the merged plain `target`; plain is authoritative, so when `richStateToPlain(targetRich) !== target` the guard drops the stale rich and the editor rebuilds it from plain. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-4-4`; **the live remote-caret overlay is deferred to Phase 4.4.1** (appended to the table + ROADMAP §4) — the presence model already carries `caret` as the seam, but rendering it needs editor→session caret reporting + offset→DOM measurement (a finicky, separable slice), so 4.4 ships the load-bearing lease/attribution/guard core (the "one editor + one viewer; lease releases on blur" DoD) and the per-segment "is editing" chip as the visible presence cue. Tests: `sync.lease` (alone→own; higher-peerId co-editor→own; lower-peerId→viewer names holder; not-on-segment reports the editor; symmetric one-owner; release; `peersOnSegment`), `sync.presence` (userId + caret round-trip), `sync.reverseBridge` (guard drops a stale targetRich once the merged plain diverges, no-op while they agree), and e2e `lan-collaboration.spec.ts` extended (**simultaneous entry → exactly one read-only viewer + one editable owner; owner blur releases the lease** via two BroadcastChannel tabs). Green: 645 unit (+9) / 28 e2e (+1) / build (flag-off has zero `GoTrueClient`; leases are local-capable so they ship in the entry as intended). - 2026-07-20 — Phase 4.4 merged in PR #58. Phase 4.4.1 built: the live remote-caret overlay deferred from 4.4 (**Milestone 4 fully complete**). `src/core/spell/offsets.ts` gains pure `mapOffsetToNode(spans, offset)` — the caret counterpart to `mapTokenToNode`, resolving a plain-text offset to a text node + local offset (a text/text boundary snaps to the earlier node's end; a tag boundary or past-the-end returns null → the overlay skips that frame). `src/features/editor/rich/richOffsets.ts` — `selectionToCaretRange(selection)` converts a live Lexical selection to plain anchor/focus offsets and `buildLeafSpans(paragraph)` produces the ordered leaf spans, both reproducing exactly the plain projection `richStateToPlain` derives (a tag contributes `{id}`, a **delete change-mark projects to '' and its subtree is skipped**) so a caret one peer reports lands on the same character in another peer's editor. `RemoteCaretOverlay.tsx` (a Lexical plugin mounted in `RichSegmentEditor` beside the spell overlay) paints a coloured caret + name label for each remote peer whose presence puts them on the segment with a caret, measured via `getElementByKey`+DOM range like `SpellUnderlinePlugin` — it never mutates the node tree and every measurement is guarded (renders nothing on failure). Reporting: the **focused** segment's editor (the only one given `onCaret`) computes `selectionToCaretRange` in its update listener and reports it through `EditorPage`→`useProjectSync`→`setActiveSegment(id, caret)`, throttled to one send per 100ms; `useProjectSync.setActiveSegment` now forwards the caret. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-4-4-1`. Tests: `spell.offsets` extended (`mapOffsetToNode`: inside/boundary/after-tag/end/out-of-range), `richOffsets` (headless: caret offsets + leaf spans agree with the plain projection for text+tag, and a **delete change-mark projects to nothing** so a caret past it collapses correctly), and e2e `lan-collaboration.spec.ts` extended (**a peer's caret renders as a live remote cursor in the other tab**). Green: 653 unit (+8) / 29 e2e (+1) / build (flag-off zero `GoTrueClient`; the overlay is local-capable and ships in the entry, no Supabase). **Milestone 4 (cloud projects + real-time collaboration) complete.** - 2026-07-20 — Phase 4.4.1 merged in PR #59. Phase 5.1 built: members & roles management (start of **Milestone 5**, roles & workflow). `supabase/migrations/0008_member_policies.sql` — adds `profiles.email` (backfilled from `auth.users` + seeded on signup) and a co-member `profiles` SELECT policy via a new `private.shares_project(other)` SECURITY DEFINER helper (the "broader read for member lookup" 0001 deferred to 5.1); switches `project_members` **update/delete to project_manager-only** (`private.has_project_role`), keeping `insert` reachable by the owner's publish self-seed OR a PM; a `enforce_min_one_pm` BEFORE UPDATE/DELETE trigger that blocks removing/demoting a project's **last** manager; and the `invite_member(project_id, email, role)` RPC (SECURITY DEFINER, PM-checked) that resolves the email → user id and upserts membership, returning NULL when no account matches (no id/email enumeration surface). Numbering deviation: ROADMAP §5.1 sketched `0006_member_policies.sql`, but `0006`/`0007` were taken (4.1 helpers + 4.3 compaction), so it ships as `0008`; and invite resolves the email **inside** the RPC rather than a client-side profiles lookup, keeping it server-side. `src/storage/cloud/members.ts` — pure client-injected `listMembers` (joins the roster to profiles for names/emails, marks self, sorts managers first), `inviteMember` (RPC → `{status:'invited'|'not_found'}`), `changeMemberRole`/`removeMember` (direct writes gated by PM-only RLS + the trigger, surfaced as thrown errors). UI: `features/projects/cloud/ManageMembersDialog.tsx` (roster; a PM gets an invite-by-email form + per-member role `` (+ read-only badge for non-PMs) lives in `ManageMembersDialog`. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-5-2`; content-level rules are client-enforced per D8 (the append-only authored log is the audit trail), stated honestly. `docs/cloud.md` (§3 migration list + §12 manual role-matrix). Tests: `workflow.rules` (full role × stage × status matrix incl. reviewed-segment suggest, final lock, local-only PM-of-self, `forcesSuggesting`), `cloud.projectCloud` extended (stage read defaults to translation + `setCloudProjectStage` update). Verified: flag-OFF `dist` has **zero** `GoTrueClient`; the tracked-changes e2e still green (local-only = PM keeps accept/reject). Green: 671 unit (+10) / 8 tracked-changes e2e / build. No new e2e (ROADMAP §5.2 asks none; the full role matrix needs three real accounts, per the §12 manual matrix). -- 2026-07-21 — Phase 5.2 merged in PR #61. Phase 5.3 built: approval workflow + unified attribution in versioning (**Milestone 5 complete → web v2 launch candidate**). `ProjectVersion` gains `authorId?` (the stable `profile.identity` author id — the *same* identity tracked-change marks, comments, and presence use, so attribution is unified across the app, D8) and `approval?: { authorId, authorName, at }`. `versionRepo.capture` now stamps `authorId` on **every** version (via `ensureLocalAuthor()`), and a new `versionRepo.signOff(projectId, liveDoc?)` records a `named` version labeled `Approved by {name}` with the `approval` metadata set (its presence marks an approval milestone). Wiring: `useWorkflowStore` gains a `useCanReview()` selector (revisor/PM; local-only → PM-of-self → true); `VersionHistoryPanel` shows a **Sign off** button beside Save version only when `canReview`, and renders approval versions with an accent `BadgeCheck` + their `Approved by …` label. No migration + no schema bump (versions are local Dexie; `authorId`/`approval` are additive unindexed fields; pre-5.3 rows simply lack them). Deviations (noted): built on the designated session branch, not `claude/revamp-phase-5-3`; sign-off gating is client-enforced via the workflow rules at the call site (D8) — the append-only authored update log remains the tamper-evident audit trail. Tests: `versionRepo` extended (every capture stamps a stable authorId consistent across captures; `signOff` records a labeled approval version whose `approval.authorId`/`at` match the version's `authorId`/`createdAt`, while a plain `saveNamed` has no `approval`). Verified: flag-OFF `dist` has **zero** `GoTrueClient`; the version-history e2e still green. Green: 673 unit (+2) / 1 version e2e / build. No new e2e (ROADMAP §5.3 asks none). **Milestone 5 (roles & workflow) complete — the web v2 launch candidate.** +- 2026-07-21 — Phase 5.2 merged in PR #61. Phase 5.3 built: approval workflow + unified attribution in versioning (**Milestone 5 complete → web v2 launch candidate**). `ProjectVersion` gains `authorId?` (the stable `profile.identity` author id — the _same_ identity tracked-change marks, comments, and presence use, so attribution is unified across the app, D8) and `approval?: { authorId, authorName, at }`. `versionRepo.capture` now stamps `authorId` on **every** version (via `ensureLocalAuthor()`), and a new `versionRepo.signOff(projectId, liveDoc?)` records a `named` version labeled `Approved by {name}` with the `approval` metadata set (its presence marks an approval milestone). Wiring: `useWorkflowStore` gains a `useCanReview()` selector (revisor/PM; local-only → PM-of-self → true); `VersionHistoryPanel` shows a **Sign off** button beside Save version only when `canReview`, and renders approval versions with an accent `BadgeCheck` + their `Approved by …` label. No migration + no schema bump (versions are local Dexie; `authorId`/`approval` are additive unindexed fields; pre-5.3 rows simply lack them). Deviations (noted): built on the designated session branch, not `claude/revamp-phase-5-3`; sign-off gating is client-enforced via the workflow rules at the call site (D8) — the append-only authored update log remains the tamper-evident audit trail. Tests: `versionRepo` extended (every capture stamps a stable authorId consistent across captures; `signOff` records a labeled approval version whose `approval.authorId`/`at` match the version's `authorId`/`createdAt`, while a plain `saveNamed` has no `approval`). Verified: flag-OFF `dist` has **zero** `GoTrueClient`; the version-history e2e still green. Green: 673 unit (+2) / 1 version e2e / build. No new e2e (ROADMAP §5.3 asks none). **Milestone 5 (roles & workflow) complete — the web v2 launch candidate.** - 2026-07-21 — Phase 5.3 merged in PR #62. Phase 6.1 built: the typed extension registry + the four MT providers re-registered as built-in addons (start of **Milestone 6**, extensions & connectors; D9 — in-process registry now, sandboxed hosting later, but the manifest contract is **final**). `src/core/extensions/types.ts` — `ExtensionManifest {id, name, version, kinds, permissions, builtIn, description?}`; `ExtensionKind = mt-provider|qa-rule|panel|import-format|export-format|storage-connector`; `ExtensionPermission = network|credentials|storage|clipboard|filesystem`. `src/core/extensions/registry.ts` — a dependency-free (no storage/React imports) singleton `extensionRegistry`: `register`/`unregister`/`get`/`has`/`list(kind?)` (id-sorted), enablement as a **disabled set** so a registered extension is enabled by default and an unknown id is treated as enabled (behaviour unchanged until something is explicitly turned off), `isEnabled`/`setEnabled`/`getDisabledIds`/`setDisabledIds`, and `subscribe`. `src/core/extensions/builtins.ts` — the four MT manifests (`mt.mymemory`/`mt.libretranslate`/`mt.ollama`/`mt.claude`; permissions `network` + `credentials` for the keyed ones) + idempotent `registerBuiltinExtensions()`. `src/extensions/mt/index.ts` — thin **wrappers** pairing each manifest to its existing `core/mt` provider (**no provider logic moved** — the existing MT unit tests are untouched and keep proving behaviour) + `registryEnabledMtProviderIds()`. `core/mt/index.ts` gains `MT_EXTENSION_ID` + `isProviderExtensionEnabled(id)` (defaults enabled when the manifest isn't registered), and `enabledProviders` now ANDs the user setting with the registry — so **disabling the addon removes the provider**; `useMTSettings.enabledIds` does the same and re-derives via `useSyncExternalStore(extensionRegistry.subscribe)` so the MT panel updates live. Wiring: `features/addons/registryPersistence.ts` (`registerExtensions()` sync + `startExtensionRegistry()` hydrating the disabled set from the new device-local `extensions.disabled` settings key and persisting subsequent changes), started from `main.tsx` before render. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-6-1`; no Add-ons **page** yet (that's 6.2) — 6.1 ships the registry + reactive resolution and proves the disable→panel path via unit tests. `docs/cloud.md` unchanged (no migration). Tests: `extensions.registry` (register/list-by-kind/default-enabled/toggle/unknown-id/hydrate+notify/unregister), `extensions.mt` (the four register as `mt-provider`; wrappers point at the real `MT_PROVIDERS`; **disabling `mt.claude` removes it from `enabledProviders` even with the user setting on** — the DoD — and re-enabling restores it). Verified: flag-OFF `dist` Supabase-free; the untouched MT unit tests + `mt-flow` e2e stay green. Green: 682 unit (+9) / 3 mt-flow e2e / build. No new e2e (ROADMAP §6.1 asks none). - 2026-07-21 — Phase 6.1 merged in PR #63. Phase 6.2 built: QA rules + formats as addons + the Add-ons page. `core/extensions/builtins.ts` now also registers `QA_EXTENSION_MANIFESTS` (one `qa.` per QA rule, kind `qa-rule`, no permissions — labels/descriptions from `QA_RULE_LABELS`) and `FORMAT_EXTENSION_MANIFESTS` (`format.xliff`/`format.docx`/`format.tmx`/`format.tbx`/`format.csv`, kinds `import-format`/`export-format`, `filesystem` permission); `BUILTIN_MANIFESTS` = MT + QA + formats, all registered by `registerBuiltinExtensions()`. `core/qa/registryRules.ts` — `isQaRuleEnabled(code)` (registry, **defaults on when the manifest isn't registered** so `runQA` stays pure and its tests are untouched) + `effectiveQaRules(toggles)` which ANDs the per-project rule toggles with the registry; `QAPanel` runs `runQA` with `effectiveQaRules(settings.qaRules)` and re-derives via `useSyncExternalStore(extensionRegistry.subscribe)` so toggling a QA addon changes QA output live. `features/addons/AddonsPage.tsx` — the `/addons` catalogue: sections per kind (Machine translation, Quality assurance, Import & export formats), each row showing the name, description, **permission chips**, and a Built-in badge; MT + QA rows get a working enable/disable toggle (enforced — MT panel + QA output react), formats are shown **Always on** (their round-trip logic stays in `core/*`; format-level gating is a follow-up, kept out to avoid touching every import/export path). New `/addons` route (lazy `AddonsPage`) + a Puzzle nav item. Deviations (noted): built on the designated session branch, not `claude/revamp-phase-6-2`; format addons are catalogue/display-only in v1 (no toggle) — only the DoD's QA + the 6.1 MT toggles are enforced. No migration. Tests: `qa.registryRules` (falls back all-on when unregistered; disabling `qa.double_space` forces it off + **removes the finding from `runQA` output** — the DoD; others unaffected), `AddonsPage` (lists MT + QA + format sections/rows; toggling a QA addon flips registry enablement; formats have no toggle). Verified: flag-OFF `dist` Supabase-free (AddonsPage its own lazy chunk); full e2e green after the nav/route addition. Green: 688 unit (+6) / 29 e2e / build. No new e2e (ROADMAP §6.2 asks none). -- 2026-07-21 — Phase 6.2 merged in PR #64. Phase 6.3 built: the Google Drive storage-connector addon (start of the connector half of **Milestone 6**). **Pure client OAuth** via Google Identity Services (GIS) — **independent of Supabase**, so the connector works for 100%-local users too (ROADMAP §6.3). New generic `src/extensions/connectors/types.ts` — a provider-agnostic `StorageConnector` (`isConfigured`/`listFiles`/`downloadFile`/`uploadFile`) + `ConnectorFile`/`ConnectorUpload` + a coarse `ConnectorError` (mirrors `MTError`), written once so 6.4's OneDrive reuses it. `src/extensions/connectors/gdrive/`: `config.ts` (lightweight gate — `googleClientId()` from `VITE_GOOGLE_CLIENT_ID`, `isGdriveConfigured()`, `isGdriveAvailable()` = configured AND registry-enabled; no GIS/REST imports so UI can decide whether to *offer* Drive without pulling the heavy code); `driveApi.ts` (the pure, unit-testable REST layer — `listDriveFiles`/`downloadDriveFile`/`uploadDriveFile`, each taking an OAuth token + an injectable `fetchImpl` like `core/mt/*`, using the narrow **`drive.file` scope** to dodge Google's restricted-scope verification, multipart upload, status→code error mapping); `gis.ts` (thin DOM boundary — injects the GSI script on first use, requests a `drive.file` token via popup, token held **in memory only**, never Dexie/localStorage); `index.ts` (`createGdriveConnector({getToken, fetchImpl})` factory pairing GIS+REST, tested with a fake token getter + fetch, plus the real `gdriveConnector` singleton). Generic `ConnectorFilePicker.tsx` lists a connector's files, filters by an `accept` predicate, and returns the downloaded `File`. `core/extensions/builtins.ts` registers `CONNECTOR_EXTENSION_MANIFESTS` (`connector.gdrive`, kind `storage-connector`, permissions `network`/`credentials`/`filesystem`); `BUILTIN_MANIFESTS` = MT + QA + formats + connectors. Wiring: `ImportDialog` gains a **From Google Drive** button (gated on `isGdriveAvailable()`; dynamically imports the connector on click → picker → sets the import file, then the existing import flow runs unchanged); `ExportDocxButton` gains **Save to Drive** (builds the same clean `.docx`, uploads via the connector); `AddonsPage` gains a **Storage connectors** section with a working enable/disable toggle (disabling hides both affordances). `deploy.yml` + `.env.example` carry `VITE_GOOGLE_CLIENT_ID` (public by design — a Web OAuth client id gated by the console's authorised-origins allow-list). Deviations (noted): built on the designated session branch, not `claude/revamp-phase-6-3`; the connector is intentionally **not** hidden behind the Supabase flag (it is pure client OAuth, so it ships in the entry like the collab leases — the heavy Drive REST is still code-split into a lazy chunk, and no connector code executes until the user clicks, via the dynamic `import()` + the `isGdriveAvailable()` gate). `docs/cloud.md` §13 (Google Cloud console setup + manual verification). Tests: `connectors.driveApi` (list builds the right query/Bearer + maps files; download `alt=media`; multipart upload asserts metadata+media in the body; 401→auth / 429→rate_limit / network-throw mapping), `connectors.gdrive` (config gating on the env var; manifest registration; `isGdriveAvailable` ANDs config with registry enablement; the factory lists/downloads-into-a-named-`File`/uploads through injected token+fetch), `ConnectorFilePicker` (lists accepted files, hides rejected, pick→download→onPick+close, error surfaced), `AddonsPage` extended (storage-connector section + `connector.gdrive` row + toggle). Verified: flag-OFF `dist` still **zero** `GoTrueClient`; the Drive REST is a separate lazy chunk. Green: 705 unit (+17) / build. No new e2e (ROADMAP §6.3 asks none; the real Drive round-trip is the `docs/cloud.md` §13 manual matrix — vitest never hits GIS or the network). +- 2026-07-21 — Phase 6.2 merged in PR #64. Phase 6.3 built: the Google Drive storage-connector addon (start of the connector half of **Milestone 6**). **Pure client OAuth** via Google Identity Services (GIS) — **independent of Supabase**, so the connector works for 100%-local users too (ROADMAP §6.3). New generic `src/extensions/connectors/types.ts` — a provider-agnostic `StorageConnector` (`isConfigured`/`listFiles`/`downloadFile`/`uploadFile`) + `ConnectorFile`/`ConnectorUpload` + a coarse `ConnectorError` (mirrors `MTError`), written once so 6.4's OneDrive reuses it. `src/extensions/connectors/gdrive/`: `config.ts` (lightweight gate — `googleClientId()` from `VITE_GOOGLE_CLIENT_ID`, `isGdriveConfigured()`, `isGdriveAvailable()` = configured AND registry-enabled; no GIS/REST imports so UI can decide whether to _offer_ Drive without pulling the heavy code); `driveApi.ts` (the pure, unit-testable REST layer — `listDriveFiles`/`downloadDriveFile`/`uploadDriveFile`, each taking an OAuth token + an injectable `fetchImpl` like `core/mt/*`, using the narrow **`drive.file` scope** to dodge Google's restricted-scope verification, multipart upload, status→code error mapping); `gis.ts` (thin DOM boundary — injects the GSI script on first use, requests a `drive.file` token via popup, token held **in memory only**, never Dexie/localStorage); `index.ts` (`createGdriveConnector({getToken, fetchImpl})` factory pairing GIS+REST, tested with a fake token getter + fetch, plus the real `gdriveConnector` singleton). Generic `ConnectorFilePicker.tsx` lists a connector's files, filters by an `accept` predicate, and returns the downloaded `File`. `core/extensions/builtins.ts` registers `CONNECTOR_EXTENSION_MANIFESTS` (`connector.gdrive`, kind `storage-connector`, permissions `network`/`credentials`/`filesystem`); `BUILTIN_MANIFESTS` = MT + QA + formats + connectors. Wiring: `ImportDialog` gains a **From Google Drive** button (gated on `isGdriveAvailable()`; dynamically imports the connector on click → picker → sets the import file, then the existing import flow runs unchanged); `ExportDocxButton` gains **Save to Drive** (builds the same clean `.docx`, uploads via the connector); `AddonsPage` gains a **Storage connectors** section with a working enable/disable toggle (disabling hides both affordances). `deploy.yml` + `.env.example` carry `VITE_GOOGLE_CLIENT_ID` (public by design — a Web OAuth client id gated by the console's authorised-origins allow-list). Deviations (noted): built on the designated session branch, not `claude/revamp-phase-6-3`; the connector is intentionally **not** hidden behind the Supabase flag (it is pure client OAuth, so it ships in the entry like the collab leases — the heavy Drive REST is still code-split into a lazy chunk, and no connector code executes until the user clicks, via the dynamic `import()` + the `isGdriveAvailable()` gate). `docs/cloud.md` §13 (Google Cloud console setup + manual verification). Tests: `connectors.driveApi` (list builds the right query/Bearer + maps files; download `alt=media`; multipart upload asserts metadata+media in the body; 401→auth / 429→rate_limit / network-throw mapping), `connectors.gdrive` (config gating on the env var; manifest registration; `isGdriveAvailable` ANDs config with registry enablement; the factory lists/downloads-into-a-named-`File`/uploads through injected token+fetch), `ConnectorFilePicker` (lists accepted files, hides rejected, pick→download→onPick+close, error surfaced), `AddonsPage` extended (storage-connector section + `connector.gdrive` row + toggle). Verified: flag-OFF `dist` still **zero** `GoTrueClient`; the Drive REST is a separate lazy chunk. Green: 705 unit (+17) / build. No new e2e (ROADMAP §6.3 asks none; the real Drive round-trip is the `docs/cloud.md` §13 manual matrix — vitest never hits GIS or the network). - 2026-07-21 — Phase 6.3 merged in PR #65. Phase 6.4 built: the OneDrive storage-connector addon (**Milestone 6 complete → extensions + connectors shipped**). Reuses the generic `StorageConnector`/`ConnectorFilePicker`/`ConnectorError` seam from 6.3 — the OneDrive slice is just a second connector paired to the same picker + import/export wiring. **Pure client OAuth**, **independent of Supabase** (works for 100%-local users too), via **`@azure/msal-browser`** (added as a runtime dep, **dynamic-import only** like `docx`, so MSAL never touches the initial bundle) with `loginPopup` (ROADMAP §6.4 — a popup, not a redirect, so no HashRouter interplay) against Microsoft Graph with the delegated **`Files.ReadWrite`** scope. `src/extensions/connectors/onedrive/`: `config.ts` (lightweight gate — `msalClientId()` from `VITE_MS_CLIENT_ID`, `isOnedriveConfigured()`, `isOnedriveAvailable()` = configured AND registry-enabled; no MSAL/Graph imports so UI can gate cheaply); `graphApi.ts` (the pure, unit-testable REST layer — `listOnedriveFiles` [root children, or `search(q=…)`; folders filtered out], `downloadOnedriveFile` [`/items/{id}/content`], `uploadOnedriveFile` [simple `PUT /root:/{name}:/content`], each taking a Graph token + injectable `fetchImpl` like `core/mt/*`, status→code error mapping); `msal.ts` (thin boundary — dynamic `import('@azure/msal-browser')`, a `PublicClientApplication` with `sessionStorage` cache, `acquireTokenSilent` then `loginPopup`; token never persisted to Dexie); `index.ts` (`createOnedriveConnector({getToken, fetchImpl})` factory + the real `onedriveConnector` singleton). `core/extensions/builtins.ts` adds the `connector.onedrive` manifest (kind `storage-connector`, permissions `network`/`credentials`/`filesystem`). Wiring: `ImportDialog` gains a **From OneDrive** button beside the Drive one (both gated on their `is…Available()`; each dynamically imports its connector on click → the shared picker → sets the import file); `ExportDocxButton` refactors its "Save to Drive" into a reusable `SaveToCloudButton` and adds **Save to OneDrive** (each button owns its saving/saved/error state, connector loaded on demand); the Add-ons **Storage connectors** section lists OneDrive automatically (with a working toggle — no page change needed). `deploy.yml` + `.env.example` + `vite-env.d.ts` carry `VITE_MS_CLIENT_ID` (an Azure AD SPA client id — public by design, gated by the app registration's redirect-URI allow-list). Deviations (noted): built on the designated session branch, not `claude/revamp-phase-6-4`; like Drive, the connector is intentionally **not** behind the Supabase flag (pure client OAuth) — MSAL + the Graph layer are dynamic-import only, so the entry stays free of `@azure/msal-browser` (verified: `loginPopup`/`PublicClientApplication` absent from the entry chunk; the msal library sits in its own lazy chunk reached only via the connector). `docs/cloud.md` §14 (Azure app-registration setup + manual verification). Tests: `connectors.graphApi` (list maps files + **skips folders**, root-children URL + Bearer, `search(q=…)` when queried, 401→auth; download `/content`; `PUT` upload asserts path/Content-Type/body + returns the item; network-throw mapping), `connectors.onedrive` (config gating on `VITE_MS_CLIENT_ID`; both connectors register as `storage-connector`; `isOnedriveAvailable` ANDs config with registry enablement; the factory lists/downloads-into-a-named-`File`/uploads through injected token+fetch), `AddonsPage` extended (`connector.onedrive` row + both connector toggles). Verified: flag-OFF `dist` still **zero** `GoTrueClient`; `@azure/msal-browser` absent from the entry (its own lazy chunk). Green: 717 unit (+12) / build. No new e2e (ROADMAP §6.4 asks none; the real OneDrive round-trip is the `docs/cloud.md` §14 manual matrix — vitest never loads MSAL or hits the network). **Milestone 6 (extensions & connectors) complete.** diff --git a/docs/revamp/prompts/phase-0-1.md b/docs/revamp/prompts/phase-0-1.md index 7645f6a..98fcf42 100644 --- a/docs/revamp/prompts/phase-0-1.md +++ b/docs/revamp/prompts/phase-0-1.md @@ -5,19 +5,23 @@ You are implementing **Phase 0.1** of the Verbalis Translation IDE revamp. **Goal**: Add a CI workflow that runs typecheck, vitest, build, and Playwright (chromium) on every PR, as the guardrail all later autonomous phases depend on. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (none) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 0.1 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: the full existing e2e suite. ## Deliver + - Branch `claude/revamp-phase-0-1` created from latest `main`. - Draft PR titled `Revamp 0.1: CI hardening`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-1-1.md b/docs/revamp/prompts/phase-1-1.md index a536a5d..d6dcebb 100644 --- a/docs/revamp/prompts/phase-1-1.md +++ b/docs/revamp/prompts/phase-1-1.md @@ -5,19 +5,23 @@ You are implementing **Phase 1.1** of the Verbalis Translation IDE revamp. **Goal**: Introduce the TrackedChange model, the ChangeMarkNode Lexical node, and the plain-text derivation semantics (D2/D3): pending inserts count, pending deletes don't. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (0.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 1.1 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-1-1` created from latest `main`. - Draft PR titled `Revamp 1.1: Change model core + ChangeMarkNode + derivation semantics`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-1-2.md b/docs/revamp/prompts/phase-1-2.md index d555d3b..058074a 100644 --- a/docs/revamp/prompts/phase-1-2.md +++ b/docs/revamp/prompts/phase-1-2.md @@ -5,19 +5,23 @@ You are implementing **Phase 1.2** of the Verbalis Translation IDE revamp. **Goal**: Flip rich editing to default-on and migrate all Playwright specs from textarea.fill() to contenteditable typing helpers. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (1.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 1.2 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: the full e2e suite (this phase's deliverable). ## Deliver + - Branch `claude/revamp-phase-1-2` created from latest `main`. - Draft PR titled `Revamp 1.2: Rich editing default-on + Playwright migration`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-1-3.md b/docs/revamp/prompts/phase-1-3.md index 1c31e66..9503c3a 100644 --- a/docs/revamp/prompts/phase-1-3.md +++ b/docs/revamp/prompts/phase-1-3.md @@ -5,19 +5,23 @@ You are implementing **Phase 1.3** of the Verbalis Translation IDE revamp. **Goal**: Make edits in suggesting mode become tracked suggestions (insert marks for typing, delete marks instead of removal), attributed to the local author. IME safety: act only on final controlled insertions after composition ends. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (1.2) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 1.3 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: tests/e2e/tracked-changes.spec.ts (create, part 1). ## Deliver + - Branch `claude/revamp-phase-1-3` created from latest `main`. - Draft PR titled `Revamp 1.3: Suggesting mode`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-1-4.md b/docs/revamp/prompts/phase-1-4.md index 096e808..1423e5b 100644 --- a/docs/revamp/prompts/phase-1-4.md +++ b/docs/revamp/prompts/phase-1-4.md @@ -5,19 +5,23 @@ You are implementing **Phase 1.4** of the Verbalis Translation IDE revamp. **Goal**: Implement accept/reject for tracked changes (live editor + headless for unmounted segments) and rework the Changes panel to list pending suggestions live. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (1.3) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 1.4 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: tests/e2e/tracked-changes.spec.ts (extend). ## Deliver + - Branch `claude/revamp-phase-1-4` created from latest `main`. - Draft PR titled `Revamp 1.4: Accept/reject + Changes panel rework`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-1-5.md b/docs/revamp/prompts/phase-1-5.md index 10ba2c5..69b136a 100644 --- a/docs/revamp/prompts/phase-1-5.md +++ b/docs/revamp/prompts/phase-1-5.md @@ -5,19 +5,23 @@ You are implementing **Phase 1.5** of the Verbalis Translation IDE revamp. **Goal**: Add CommentMarkNode range anchoring and threaded replies to segment comments, plus a project-wide Comments panel. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (1.2) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 1.5 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: tests/e2e/comments-anchored.spec.ts (create). ## Deliver + - Branch `claude/revamp-phase-1-5` created from latest `main`. - Draft PR titled `Revamp 1.5: Range-anchored threaded comments`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-1-6.md b/docs/revamp/prompts/phase-1-6.md index 28d534e..93e48d5 100644 --- a/docs/revamp/prompts/phase-1-6.md +++ b/docs/revamp/prompts/phase-1-6.md @@ -5,19 +5,23 @@ You are implementing **Phase 1.6** of the Verbalis Translation IDE revamp. **Goal**: Gate segment confirm on no-pending-changes, add next/prev-change keyboard navigation and palette commands, pin review panels, and add the show/hide marks toggle. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (1.4, 1.5) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 1.6 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: tests/e2e/tracked-changes.spec.ts (extend). ## Deliver + - Branch `claude/revamp-phase-1-6` created from latest `main`. - Draft PR titled `Revamp 1.6: Review polish: confirm gating, navigation, attribution`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-2-1.md b/docs/revamp/prompts/phase-2-1.md index 8f94f66..ff3246f 100644 --- a/docs/revamp/prompts/phase-2-1.md +++ b/docs/revamp/prompts/phase-2-1.md @@ -5,19 +5,23 @@ You are implementing **Phase 2.1** of the Verbalis Translation IDE revamp. **Goal**: Add documents/blocks/assets tables (Dexie v6) with the block model, backfill existing monolingual projects from sourceMeta.blockIndex, and stamp Segment.blockId on import. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (0.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 2.1 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-2-1` created from latest `main`. - Draft PR titled `Revamp 2.1: Document/block schema (Dexie v6) + backfill`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-2-2.md b/docs/revamp/prompts/phase-2-2.md index 4f586cf..82b44c8 100644 --- a/docs/revamp/prompts/phase-2-2.md +++ b/docs/revamp/prompts/phase-2-2.md @@ -5,19 +5,23 @@ You are implementing **Phase 2.2** of the Verbalis Translation IDE revamp. **Goal**: Upgrade DOCX import with a mammoth styleMap, image capture into assets, inline run capture (bold/italic/underline/sub/sup/links), and table blocks — keeping the ParsedSegment contract. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (2.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 2.2 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-2-2` created from latest `main`. - Draft PR titled `Revamp 2.2: DOCX import fidelity upgrade`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-2-3.md b/docs/revamp/prompts/phase-2-3.md index 6bae413..95cf717 100644 --- a/docs/revamp/prompts/phase-2-3.md +++ b/docs/revamp/prompts/phase-2-3.md @@ -5,19 +5,23 @@ You are implementing **Phase 2.3** of the Verbalis Translation IDE revamp. **Goal**: Add a live document preview that renders the block tree with translated segments substituted, with source/target/side-by-side modes and click-to-focus. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (2.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 2.3 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: tests/e2e/document-preview.spec.ts (create). ## Deliver + - Branch `claude/revamp-phase-2-3` created from latest `main`. - Draft PR titled `Revamp 2.3: Document preview pane`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-2-4.md b/docs/revamp/prompts/phase-2-4.md index a69ee96..5655018 100644 --- a/docs/revamp/prompts/phase-2-4.md +++ b/docs/revamp/prompts/phase-2-4.md @@ -5,19 +5,23 @@ You are implementing **Phase 2.4** of the Verbalis Translation IDE revamp. **Goal**: Export the translated document as a clean .docx (dynamic import('docx')) reconstructed from the block tree + targetRich formatting runs; pending changes export as accepted-preview with a warning. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (2.2, 2.3) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 2.4 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: download-flow assertion in tests/e2e/document-preview.spec.ts. ## Deliver + - Branch `claude/revamp-phase-2-4` created from latest `main`. - Draft PR titled `Revamp 2.4: Clean DOCX export`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-3-1.md b/docs/revamp/prompts/phase-3-1.md index ba77e37..154993e 100644 --- a/docs/revamp/prompts/phase-3-1.md +++ b/docs/revamp/prompts/phase-3-1.md @@ -5,10 +5,12 @@ You are implementing **Phase 3.1** of the Verbalis Translation IDE revamp. **Goal**: Add the lazy Supabase client (env-flagged, dynamic import, flowType 'pkce'), the pre-router ?code= exchange bootstrap for HashRouter, sign-in UI (Google + magic link), profiles migration + RLS, and docs/cloud.md. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (0.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 3.1 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-3-1` created from latest `main`. - Draft PR titled `Revamp 3.1: Supabase bootstrap + Google/magic-link auth + PKCE`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-3-2.md b/docs/revamp/prompts/phase-3-2.md index 6c90978..388842a 100644 --- a/docs/revamp/prompts/phase-3-2.md +++ b/docs/revamp/prompts/phase-3-2.md @@ -5,10 +5,12 @@ You are implementing **Phase 3.2** of the Verbalis Translation IDE revamp. **Goal**: Add Azure and Apple OAuth buttons gated by VITE_AUTH_PROVIDERS, and the account settings section (display name, linked identities, sign out). Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (3.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 3.2 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-3-2` created from latest `main`. - Draft PR titled `Revamp 3.2: Microsoft + Apple providers + account settings`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-3-3.md b/docs/revamp/prompts/phase-3-3.md index 6d21412..6aa0397 100644 --- a/docs/revamp/prompts/phase-3-3.md +++ b/docs/revamp/prompts/phase-3-3.md @@ -5,10 +5,12 @@ You are implementing **Phase 3.3** of the Verbalis Translation IDE revamp. **Goal**: Sync allowlisted settings keys (editor prefs, layout, lookup, spell) to user_settings with per-key LWW; MT API keys never sync; Dexie stays the read path. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (3.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 3.3 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-3-3` created from latest `main`. - Draft PR titled `Revamp 3.3: Synced preferences/settings/layout`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-3-4.md b/docs/revamp/prompts/phase-3-4.md index 06126b7..2bcfb73 100644 --- a/docs/revamp/prompts/phase-3-4.md +++ b/docs/revamp/prompts/phase-3-4.md @@ -5,10 +5,12 @@ You are implementing **Phase 3.4** of the Verbalis Translation IDE revamp. **Goal**: Sync glossary and personal TM rows (not bundled corpora) with cursor-based incremental row sync, LWW, and tombstones (Dexie v7). Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (3.3) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 3.4 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-3-4` created from latest `main`. - Draft PR titled `Revamp 3.4: Personal term bank + TM sync`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-4-1.md b/docs/revamp/prompts/phase-4-1.md index e9ff2ce..d9878a6 100644 --- a/docs/revamp/prompts/phase-4-1.md +++ b/docs/revamp/prompts/phase-4-1.md @@ -5,10 +5,12 @@ You are implementing **Phase 4.1** of the Verbalis Translation IDE revamp. **Goal**: Add the cloud project schema (projects, project_members with roles, ydoc_state, ydoc_updates) with member-scoped RLS, plus publishProject/openCloudProject flows. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (3.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 4.1 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-4-1` created from latest `main`. - Draft PR titled `Revamp 4.1: Cloud project schema + RLS + publish/join`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-4-2.md b/docs/revamp/prompts/phase-4-2.md index 36283d2..73ea745 100644 --- a/docs/revamp/prompts/phase-4-2.md +++ b/docs/revamp/prompts/phase-4-2.md @@ -5,10 +5,12 @@ You are implementing **Phase 4.2** of the Verbalis Translation IDE revamp. **Goal**: Implement the SyncTransport interface over Supabase Realtime broadcast with 60KB chunking and debounced Y.mergeUpdates batching; BroadcastChannel/LAN paths untouched. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (4.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 4.2 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-4-2` created from latest `main`. - Draft PR titled `Revamp 4.2: SupabaseRealtimeTransport + chunking`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-4-3.md b/docs/revamp/prompts/phase-4-3.md index 4fb3e08..b8755f7 100644 --- a/docs/revamp/prompts/phase-4-3.md +++ b/docs/revamp/prompts/phase-4-3.md @@ -5,10 +5,12 @@ You are implementing **Phase 4.3** of the Verbalis Translation IDE revamp. **Goal**: Add the ydoc persistence loop: catch-up from ydoc_state + updates on open, debounced appends, and optimistic client-side compaction via the claim_compaction RPC. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (4.2) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 4.3 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-4-3` created from latest `main`. - Draft PR titled `Revamp 4.3: Postgres persistence loop`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-4-4.md b/docs/revamp/prompts/phase-4-4.md index 2781992..25b9659 100644 --- a/docs/revamp/prompts/phase-4-4.md +++ b/docs/revamp/prompts/phase-4-4.md @@ -5,10 +5,12 @@ You are implementing **Phase 4.4** of the Verbalis Translation IDE revamp. **Goal**: Add remote cursors/selection overlays, per-segment edit leases with deterministic tie-break, and profile-based presence; guard reverseBridge against stale-LWW rich mismatches. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (4.3, 1.6) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 4.4 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`, plus Playwright: tests/e2e/lan-collaboration.spec.ts (extend, two-tab BroadcastChannel). ## Deliver + - Branch `claude/revamp-phase-4-4` created from latest `main`. - Draft PR titled `Revamp 4.4: Live collab UX: cursors, leases, attribution`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-5-1.md b/docs/revamp/prompts/phase-5-1.md index b4d6fb4..a6f170d 100644 --- a/docs/revamp/prompts/phase-5-1.md +++ b/docs/revamp/prompts/phase-5-1.md @@ -5,10 +5,12 @@ You are implementing **Phase 5.1** of the Verbalis Translation IDE revamp. **Goal**: Add member management (invite by email, change role, remove) with PM-only RLS policies and a Members panel. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (4.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 5.1 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-5-1` created from latest `main`. - Draft PR titled `Revamp 5.1: Members & roles management`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-5-2.md b/docs/revamp/prompts/phase-5-2.md index 4c22d61..edbc55b 100644 --- a/docs/revamp/prompts/phase-5-2.md +++ b/docs/revamp/prompts/phase-5-2.md @@ -5,10 +5,12 @@ You are implementing **Phase 5.2** of the Verbalis Translation IDE revamp. **Goal**: Add pure workflow rules (role x stage -> capabilities), project stage/deadline (PM-only), forced suggesting for translators in review stage, and role-gated accept/reject; local-only projects stay fully permissive. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (5.1, 1.6) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 5.2 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-5-2` created from latest `main`. - Draft PR titled `Revamp 5.2: Role-gated editing workflow`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-5-3.md b/docs/revamp/prompts/phase-5-3.md index 4a6d170..bb28a33 100644 --- a/docs/revamp/prompts/phase-5-3.md +++ b/docs/revamp/prompts/phase-5-3.md @@ -5,10 +5,12 @@ You are implementing **Phase 5.3** of the Verbalis Translation IDE revamp. **Goal**: Add authorId/approval to versions, a revisor/PM sign-off action, and unify author identity across marks, comments, versions, and presence. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (5.2) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 5.3 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. @@ -16,9 +18,11 @@ Before touching code: - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-5-3` created from latest `main`. - Draft PR titled `Revamp 5.3: Approval workflow + attribution in versioning`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-6-1.md b/docs/revamp/prompts/phase-6-1.md index f03506d..e96e448 100644 --- a/docs/revamp/prompts/phase-6-1.md +++ b/docs/revamp/prompts/phase-6-1.md @@ -5,19 +5,23 @@ You are implementing **Phase 6.1** of the Verbalis Translation IDE revamp. **Goal**: Add the typed ExtensionRegistry with manifests/permissions and re-register the four MT providers as built-in addons (pure wrappers, no provider logic moves). Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (0.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 6.1 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-6-1` created from latest `main`. - Draft PR titled `Revamp 6.1: Extension registry + MT providers as built-in addons`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-6-2.md b/docs/revamp/prompts/phase-6-2.md index 512b25d..cd840da 100644 --- a/docs/revamp/prompts/phase-6-2.md +++ b/docs/revamp/prompts/phase-6-2.md @@ -5,19 +5,23 @@ You are implementing **Phase 6.2** of the Verbalis Translation IDE revamp. **Goal**: Wrap QA rules and import/export formats as addon contributions and ship the /addons catalogue page with enable/disable and permission display. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (6.1) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 6.2 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-6-2` created from latest `main`. - Draft PR titled `Revamp 6.2: QA rules + formats as addons + Add-ons page`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-6-3.md b/docs/revamp/prompts/phase-6-3.md index bd8fef4..28102e1 100644 --- a/docs/revamp/prompts/phase-6-3.md +++ b/docs/revamp/prompts/phase-6-3.md @@ -5,19 +5,23 @@ You are implementing **Phase 6.3** of the Verbalis Translation IDE revamp. **Goal**: Add the Google Drive storage-connector addon (GIS token client, drive.file scope) with the generic ConnectorFilePicker, wired into import and DOCX export. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (6.1, 2.4) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 6.3 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-6-3` created from latest `main`. - Draft PR titled `Revamp 6.3: Google Drive connector`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/revamp/prompts/phase-6-4.md b/docs/revamp/prompts/phase-6-4.md index bce4686..41c64ea 100644 --- a/docs/revamp/prompts/phase-6-4.md +++ b/docs/revamp/prompts/phase-6-4.md @@ -5,19 +5,23 @@ You are implementing **Phase 6.4** of the Verbalis Translation IDE revamp. **Goal**: Add the OneDrive storage-connector addon via dynamic @azure/msal-browser (loginPopup) and Microsoft Graph, reusing the generic picker. Before touching code: + 1. Read `docs/revamp/STATUS.md` — confirm this phase is `pending` and its dependencies (6.3) are `done`. If not, follow STATUS rules instead (fix the in-review PR, or stop and report). 2. Read `docs/revamp/ROADMAP.md` §4 Phase 6.4 — that section is the **complete and only** scope definition (files to create/modify, key design points, tests, DoD). Also read the §3 architecture decisions it cites. Do not exceed that scope. ## Invariants (non-negotiable) + - Local-only mode keeps working fully, with no account and no behavior regressions. - The `richStateToPlain` contract is preserved (`src/core/editor/richText.ts`). - Dexie remains the local source of truth. - All existing tests keep passing. ## Verify + `pnpm typecheck && pnpm test:unit && pnpm build`. No new e2e required unless ROADMAP §4 says otherwise. ## Deliver + - Branch `claude/revamp-phase-6-4` created from latest `main`. - Draft PR titled `Revamp 6.4: OneDrive connector`. - Update `docs/revamp/STATUS.md` in the same PR: this phase -> `in-review (PR #n)`, and record the merge of the previous phase if STATUS is stale. diff --git a/docs/roadmap-professional-features.md b/docs/roadmap-professional-features.md index 3678331..52ed9bd 100644 --- a/docs/roadmap-professional-features.md +++ b/docs/roadmap-professional-features.md @@ -14,17 +14,17 @@ below. memoQ is powerful and dense — a ribbon with ~120 commands, modal resource consoles, and a learning curve measured in weeks. Verbalis copies memoQ's -*capabilities*, not its *surface*. Every feature below is restated as "what +_capabilities_, not its _surface_. Every feature below is restated as "what memoQ does" → "the Verbalis‑native, simpler treatment" → "where it plugs into the current code". The plan is built on three decisions already made with the maintainer: -| Decision | Choice | -|---|---| -| LAN collaboration | **Tauri desktop peers** with mDNS auto‑discovery + CRDT sync — every install is a real peer, no cloud, no central server | -| Rich editor core | **Lexical** (Meta) replaces the plain `