diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 52f3fc7..8a2e661 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -39,8 +39,6 @@ jobs: run: pnpm build env: TASKNOTES_PLANNER_URL: https://planner.tasknotes.dev - VITE_MDBASE_CONNECT_URL: https://connect.mdbase.dev - VITE_MDBASE_CONNECT_LOOPBACK_URL: http://127.0.0.1:28485 - name: Deploy Cloudflare Pages run: pnpm dlx wrangler@4.114.0 pages deploy dist --project-name=tasknotes-planner --branch=main env: diff --git a/AGENTS.md b/AGENTS.md index 3dc682a..a80d55b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ Read `README.md` and `DESIGN.md` before changing user-facing behavior or visuals. The app consumes the portable `tasknotes.task` contract directly through -`@mdbase-dev/connect`. Record access must remain contract-scoped even though +`@mdbase-dev/sdk`. Record access must remain contract-scoped even though the app uses full-collection authorization for the saved-view APIs required by `tasknotesPlanner` Bases. Do not import source code from the TaskNotes application repository. Date, dependency, and field behavior that belongs to diff --git a/README.md b/README.md index a7be51d..539a0f1 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,9 @@ pnpm deploy:dev ``` This deploys only the Cloudflare `staging` branch at -`https://staging.tasknotes-planner.pages.dev`, using staging Connect and the -staging desktop connector on `127.0.0.1:28486`. It does not change production. +`https://staging.tasknotes-planner.pages.dev`, built for the mdbase staging +environment. `pnpm deploy:lab` deploys the `lab` branch for LAB. Neither +changes production. Production and development builds generate separate mdbase manifests. The production declaration contains only the `planner.tasknotes.dev` callback; @@ -147,19 +148,16 @@ saved-view source deletion. Saved-view deletion is not record deletion, and no independent operation permission is requested. Task creation also requires the `add` file action; an older approval without it needs renewed consent. Record deletion, arbitrary file operations, offline replicas, and background scheduling -remain excluded. See -[the migration guard and rollout order](docs/capability-v2-migration.md): the -vendored beta96 v2 candidate SDK is source-bound development evidence, not a published or -signed release. This v2 draft must not deploy before the qualified v2 writer. +remain excluded. The production declaration assumes `https://planner.tasknotes.dev/`. Update the homepage, icon, and redirect URI together if the deployment origin changes. -## New SDK migration +## mdbase SDK -The existing Connect session remains the default. A native SDK data adapter is -being prepared separately; it is not yet a browser sign-in mode. See -[its behavior, pinned artifact, and remaining dependencies](docs/new-sdk.md). +Planner opens collections through the published `@mdbase-dev/sdk` and the +shared `@mdbase-dev/ui` collection gate. See +[the SDK data layer](docs/new-sdk.md). ## Repository structure @@ -168,5 +166,5 @@ src/components React application and Gantt surface src/data mdbase connection and repository adapters src/domain task and timeline semantics public web and mdbase application manifests -vendor pinned development mdbase packages and contract packs +vendor pinned mdbase contract packs ``` diff --git a/docs/capability-v2-migration.md b/docs/capability-v2-migration.md deleted file mode 100644 index 353006b..0000000 --- a/docs/capability-v2-migration.md +++ /dev/null @@ -1,215 +0,0 @@ -# Capability v2 migration — do not deploy - -This is an unpublished SDK + v2 integration draft. **Do not merge into -auto-deploying main, push, publish, or deploy this branch.** The beta95 SDK -upgrade does not authorize release of this v2 declaration. - -Required rollout order: - -1. Deploy beta95 readers only, retaining fresh issuance v1 (`fresh=1`). -2. Subsequently qualify and deploy the v2 writer, retaining beta95 as rollback. -3. Only then qualify and release consumers declaring v2, including Planner. - -The SDK may be beta95 before step 2; the consumer v2 manifest must not release -before the writer. Parent-owned LAB and signed staging/release evidence remain -separate prerequisites. Never substitute workspace packages or patch vendor -archives to hide an SDK defect. - -## Current beta96 v2 candidate provenance - -The active SDK pins now use authentic `0.1.0-beta.96` SHA-qualified development -packs from committed product source -`56ed32ffde0544ca497e854702d87ef2c175b954`, supplied by the parent at -`/home/calluma/projects/mdbase-connect/.ops/artifacts/v2-sdk-56ed32ffde05`. -The parent generated these with `pack-consumer-sdk.mjs` for phase -`v2-enablement`, supporting capability contracts `[1, 2]`. -These are **not signed Q artifacts or an npm publication**. - -The existing vendor subset is preserved. Every copied archive was checked against -the supplied manifest's byte length and SHA-512, and its package version was -checked as `0.1.0-beta.96`. `vendor/mdbase-connect-sdk.json` records the new -revision, filenames, sizes and SHA-512 values. Dependency and transitive override -pins are updated together; package-manager installation regenerates the lockfile. -No archive is patched or relabelled and no package dependency is added. - -The beta95 account below is historical, superseded for active pins by this -candidate. Release remains held: the parent owns rollout ordering, live/native -acceptance, and repinning immutable final release artifacts before consumer -publication if required. Existing v1 sessions remain retained; updated consent -requires explicit reauthorization without silent conversion or fallback. - -## Historical beta95 provenance - -The previous pins were **beta95-Q**, not enabled-candidate artifacts. Consumer -v2 enablement is authorized in the coordinated cycle; core issuance and SDK -generation remain parent-owned. At that stage, the parent needed to supply actual enabled-candidate -`package:consumer` output and update the connect/protocol/sync archives, -`vendor/mdbase-connect-sdk.json`, dependency/override pins and `pnpm-lock.yaml` -together. Record actual source revision, versions, sizes and hashes; do not -relabel beta95-Q or pin dirty Writer source. Repeat consumer checks against -those exact artifacts before live acceptance. - -`vendor/mdbase-connect-sdk.json` is the connect/protocol/sync subset of product -`package:consumer` output from clean source -`408c67bc10f128e0833f0da62cb3efb9d94657d7`, version `0.1.0-beta.95`, supplied -in `/tmp/beta95-consumer-sdk.fmn7sH`. The parent verified artifact sizes, -SHA-512 values and versions. These are **SOURCE-BOUND local-build artifacts**, -not signed-image or published-registry release evidence. The old beta85 archives -are replaced, dependencies/overrides and the frozen lockfile use beta95 without -unrelated upgrades. Sync is retained and overridden to the matching artifact; -this connect package currently depends only on protocol, so Planner does not -import or install sync merely to claim replica support. - -## Inventory and consent - -The production generator is `scripts/planner-manifest.mjs`, invoked through -`pnpm manifest` / `scripts/write-manifest.mjs`. Its previous v1 requirements were: - -- `collection.inspect`, `collection.setup.apply` -- `definitions.update`, `definitions.type-pack.apply` -- `records.watch`, `records.read`, `records.query`, `records.update` -- `views.list`, `views.execute`, `views.source.read`, `views.source.create`, - `views.source.update` - -It now declares `contract_version: 2` with exactly these required atomic groups -and no optional capabilities: - -| Group | Consent meaning | -| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `collection.read` | Describe, changes, read/query, list/execute views, read view sources, validate records, and read types. | -| `records.create` | Create TaskNotes tasks through their implementing record contract. | -| `records.edit` | Update and rename records; no record deletion. | -| `views.manage` | Create, update, and delete saved-view sources. Whole-group consent intentionally includes saved-view deletion, not record deletion. | -| `definitions.manage` | Create/update types and assess/apply type packs. | - -Compared with the previous operation union, consent additionally covers record -validation, type reading, record rename, type creation, and saved-view source -deletion. These are atomic group meanings, not independently requested operation -permissions. Setup assessment/application is handled by Connect's separate -setup flow, with the existing provisions unchanged. - -Planner now requests `records.create` for its add-task workflow, together with -the `add` file action. Existing approvals must grant this expanded declaration; -the app cannot treat an old edit-only grant as creation authority. -No `records.delete`, `offline.replica`, or `background.schedule` is requested. -Collection file actions `list`, `read`, `add`, `replace` and `move` accompany -`collection.read`, `records.create` and `records.edit`. Full-collection authorization remains -necessary for Bases, while the application continues to select and mutate task -records through `tasknotes.task` only. No app capability aliases or fabricated -SDK groups were introduced; session state handling remains SDK-owned. - -## Preserved boundaries - -The exact TaskNotes contracts, type-pack provisions, configuration requirement -and provision, and production callback are unchanged. Generated paths remain -`public/.well-known/mdbase-app.json` and `src/generated-mdbase-app.json`. -Development uses the existing documented `http://127.0.0.1:4174` origin only: -the invalid cross-origin localhost callback is removed. Runtime uses the declared -callback and rejects opening a connection from an undeclared origin with an -explicit URL instruction; origins are not broadened. - -`scripts/planner-manifest.mjs` now invokes the installed protocol's strict -`parseAppManifest`, so normal manifest/build/verify fail on invalid declarations. -Local HTTP is accepted only in development. No v2-to-v1 compatibility fallback -is used. SDK session readiness owns consent, exact contract verification and -setup; the gate requires a user click to review updated access and never -silently upgrades the prior grant. - -## Prior declaration-only evidence (superseded by beta95 checks below) - -- `pnpm install --frozen-lockfile`, `pnpm manifest`: passed. -- `pnpm test`: 9 files, 56 tests passed, including exact group and generated - manifest checks. -- `pnpm typecheck`, `pnpm lint`, `pnpm build`: passed. Build success does not - demonstrate SDK runtime compatibility. -- Both production generated manifests passed the sibling feature Connect - `packages/protocol/dist/manifest.js` `validateAppManifest` validator. This was - a read-only local import; no Connect edits or production API calls. -- The unchanged development manifest fails that validator even with - `allowLocal: true`: `/redirect_uris/1` uses localhost rather than the homepage's - 127.0.0.1 origin. Callback behavior was deliberately preserved, not silently - changed to satisfy validation. -- The pinned beta85 SDK constructor accepts the declaration with explicit - in-memory storage, but its real `effectiveCapabilities` evaluator throws - `TypeError: APPLICATION_CAPABILITY_DEFINITIONS[capability] is not iterable` - for the v2 declaration. This deterministic, no-network probe confirms the - release blocker despite passing mocked application tests and build checks. - -## Beta95 integration evidence (Node 24.19.0) - -- Parent reran frozen install and full `pnpm verify`: formatting, typecheck, - lint, all 65 tests, strict production manifest validation and build pass. - Lockfile formatting churn was removed; the remaining diff is SDK-related. - Parent independently verified all vendored artifact sizes and SHA-512 hashes. -- Genuine installed SDK construction, manifest loading and v2 capability - evaluation pass without network; stale v1 operation sets and denied groups - require authorization instead of throwing or silently upgrading. -- Real SDK application-session tests use fixture transport only: stale - registration with sufficient operations still requires explicit consent; - denied consent cannot apply setup; declaration mismatch and denied describe - require authorization; missing exact contracts block readiness; matching - contracts and current setup reach verified ready. -- UI regressions cover denied consent, explicit reauthorization, stale grants, - startup/callback lifecycle and setup review. No timeout or budget is raised. - -No browser/LAB acceptance, production smoke API, deployment, commit, or push is -performed. E2E is demo-only but requires a browser, prohibited for this task; -parent-owned isolated execution remains required. Parent LAB must verify real -consent denial/revocation, old-grant reauthorization, contract/type-pack setup, -Bases full-collection access and contract-scoped task edits with the qualified -writer and beta95 rollback. Local fixtures are not release acceptance. - -## Coordinated consumer verification — 2026-09-09 - -With Node 24.19.0, the initial `pnpm test` passed 11 files / 65 tests; -`pnpm typecheck` and `pnpm build` passed, including strict manifest validation -and static callback verification. After extending repository regressions, -`pnpm test` passed 11 files / 66 tests and `pnpm typecheck` passed again. -The added assertions cover contract-scoped query/read/schedule updates with -revision guards, and saved-view updates preserving unrelated views/options -using the freshly read source revision. No runtime failure was found and no -additional group was requested to satisfy a test. - -Parent LAB still needs fresh consent and stale v1 reauthorization, denied and -revoked access without clearing unrelated state, exact-contract/setup review, -date/dependency/status edits, saved-view list/execute/create/update, authority -failures and recovery across hosted and connected-computer collections. Tests -and builds here used fixtures/local artifacts only; no browser, LAB, smoke, -deployment or publication ran. - -## Beta96 candidate integration results — 2026-09-09 - -- Full `pnpm verify`: passed, including formatting, typecheck, lint, - **11 files / 66 tests**, strict manifest parsing and production build. -- Existing contract-scoped record/schedule and full-collection saved-view CAS - regression assertions are preserved. - -The installed beta96 SDK checks `/health` for -`application-authorization-v2-issuance` before fresh v2 authorization. TaskNotes, -Workouts and Pickle hermetic transport fixtures now model that endpoint; they -still assert exact signed intent and denial. Workouts additionally verifies a -v1-only server returns `capability_contract_incompatible` without an authorization -request or navigation. No production application source, authority declaration, -retained-session migration, or native route changed in this integration pass. - -All checks used Node `v24.19.0` via the requested PATH. Initial cache writes -failed with `EROFS`; existing package-manager caches were copied into the private -log directory under `/tmp`, then offline installation succeeded in all four -worktrees (`pnpm install --no-frozen-lockfile --prod=false --offline --store-dir …` -or `npm install --include=dev --offline --cache …`). Lockfiles were regenerated -by package managers, with formatting restored where required. Every unrelated -package/snapshot lock entry is structurally unchanged from the starting draft. -No dependencies, audit waivers or timeout increases were introduced. - -These are local fixture/unit results, not live browser, daemon, or native -acceptance. Parent acceptance still needs fresh registration and explicit v1 -reauthorization, retained valid v1 grants, denial/revocation and recovery, exact -contract/setup and readonly view behavior, typed records and revision guards, -TaskNotes/Planner saved views, Workouts timers, and actual opt-in notifications -and opaque Pickle wakeups across the supported providers and native callbacks. -Parent owns release ordering and immutable final artifact repinning before -publication if required. Reader, bundled clients and canonical Connect were not -edited; no agents, browser operations, credentials, live service acceptance, -commits, pushes, publishing or deployments were used. - -Private logs: `/tmp/v2-consumer-sdk-integration-20260909T130351/tasknotes-planner`. diff --git a/docs/new-sdk.md b/docs/new-sdk.md index 0c90881..e3667b8 100644 --- a/docs/new-sdk.md +++ b/docs/new-sdk.md @@ -1,25 +1,13 @@ -# New SDK data adapter +# mdbase SDK data layer -## Current unified SDK intake — 2026-10-14 - -SDK `72f8ea6caeafa4b089d3ee14c3b38ce492511a2c` replaces the prior -`3f417b6d` intake below. Original archive SHA256: -`d58ee0fb78f2a3bfb49cc70e92bea69fe2292eb2b1cfff0cd9c222efba6d9374`. -The ordinary Sign in / Add chooser owns one popup and the original Native -preparation, callback and custody ACK. The callback page delivers only; the -original SDK closes after completion. No app-side alternate protocol, scope, -key transfer or grant-store migration is introduced. Application declaration and -storage identities remain unchanged. Tests and LAB builds do not prove Native -admission, READ/edit/reload or production release qualification. - -## Prior intake notes (superseded SDK pin) - -`NextPlannerRepository` implements `PlannerRepository` directly on the pinned -`@mdbase-dev/sdk` package. The existing Connect session remains the application -default. There is **no working new-SDK browser sign-in switch in this change**; -this data adapter is a foundation for the shared SDK web sign-in integration. -The opt-in SDK sign-in UI below does not change the default runtime. -Do not use it as evidence of launch readiness or a deployed app. +Planner opens collections through the published `@mdbase-dev/sdk` in every +deployment. `src/data/sdk-application.ts` declares the application (canonical +manifest, homepage origin and the build's `VITE_MDBASE_ENV`: `lab`, `staging`, +or production when unset) and creates the page's one SDK entry. The shared +`@mdbase-dev/ui` collection gate owns sign-in, collection choice, setup and +problems; Planner supplies no auth, grant or setup engine of its own. +`NextPlannerRepository` implements `PlannerRepository` over the selected +connection's borrowed client. ## Data behavior @@ -74,207 +62,3 @@ View writes settle through `client.save()` too. A saved result requires an unprotected current full-source readback and native discovery of the same UUID/source revision/original ordinal. No separate autosave/session engine is introduced. - -Shared web sign-in and manifest-driven setup must be integrated from the SDK; -Planner does not implement private auth/route/grant protocols or a second setup -engine. The application will not switch its data path until those dependencies -are available and tested. - -## Planned SDK application entry - -The integration target is one public SDK entry: declare the application, obtain -its lifecycle session, authorize in a user gesture, select a collection, and use -its current scoped connection for records, files and people. Since `7193fd0d` -the development archive carries the live composition (LAB environment only); -it is not release- or native-qualified. -TaskNotes is the first consumer and Planner follows after qualified delivery. - -Planner supplies only its declaration: identity, name, origin/environment and -manifest. The SDK owns authenticated native assets/trust, Worker and SQL plumbing, -DB names, setup journal, current semantic/catalog readiness, original recovery -and the existing people directory engine. New namespaces are SDK-derived from -registered app identity. Optional storage compatibility is declaration data for -existing installations, not an app-owned engine or permission to replace them. -Planner has no original native journal requiring a compatibility override. - -`dev.tasknotes.planner` is manifest identity, not evidence of a registered native -application UUID. The typed registration binding must be settled before database -allocation; prototype UUIDs or namespace strings cannot stand in for registration. -The scoped records service must retain the same public borrowed `MdbaseClient` -used by `NextPlannerRepository` and `NextPlannerViews`, including receipts, -watches and native AppBases/source APIs. Accessors do not add consent: Planner's -manifest omits files and people. Only the canonical shared manifest compiler may -interpret authorization; no duplicate compiler or implicit widening is supplied. - -The local per-app loader/factory/inventory prototypes are frozen, source-only -experiments, not an integration recipe. Do not copy them into another app or the -SDK. Native open/readiness/recovery requires qualified delivery and genuine local -acceptance, independently of source tests or archive qualification. - -## Public-entry source port - -The port branch targets the public application entry at SDK dev source -`3f417b6d941a42444f17568d7489071d23fcb141`, original `sdk-dev-3f417b6d` cut (earlier pins, including distinct `sdk-dev-2e2ae687-n1`, remain in history). `createPlannerApplication` -supplies only the unchanged manifest (logical ID, name, homepage origin) and the -environment; the SDK registers the manifest and rebinds to the actual registered -ID before any storage. No prototype UUID or storage override is used. `App` -accepts that SDK entry optionally. - -The existing ledger receives `NextPlannerRepository` over the selected -connection's **same borrowed client**, only when the SDK reports current semantic -readiness and a current selected connection. UI teardown releases repository -watches/scope, never the SDK client. Collection changes await SDK selection -clearing and show close refusals without discarding the current ledger. Sign-out -waits for successful physical close; late actions from retired UI owners cannot -publish sign-out for a replacement owner. Identity uses the SDK's existing -canonical email or GitHub provider/login fields, never an account UUID or a -fabricated email. - -When the -SDK reports `setup_required` (status or readiness assessment), the gate offers -**Set up TaskNotes Planner**, which reads the SDK setup plan and reviews its -plain summary, changes, conflicts and refusal. Confirm applies that original -plan; nothing-to-review keeps `configure()`. Unknown outcomes re-assess through -the SDK without claiming no writes or automatically submitting a fresh mutation. -Planner keeps no setup engine and never infers READY from a configure/apply -result: the ledger still waits for current SDK readiness. A failed or -missing settled readiness check offers **Check again**, which calls the SDK's -`assess()` with the current caller signal. Incompatible collections and retired -connections show a clear explanation and **Choose another collection**, rather -than an endless Opening message. Active SDK checks remain loading states; no -repository is allocated until aggregate readiness and the original connection -are current. Registered binding and popup reopen/expiry helpers remain producer work. - -A link that requests an unapproved collection (`selection.state` -`approval_required`) or an invalid one offers **Approve this collection** or -**Choose another collection** (`chooseAnother()`); Planner never falls back to -another collection. - -**Live hookup:** `plannerSdk()` is the page's one `MdbaseConnect`. Builds with -`VITE_MDBASE_ENV=lab` lazily render `App` over it (`components/root.tsx`); other -builds keep the existing Connect runtime because the SDK refuses production -(`application_production_trust_unavailable`). A LAB build also needs -`TASKNOTES_PLANNER_URL` set to its served origin so the manifest homepage matches. -`vite.config.ts` excludes the SDK from dev pre-bundling and emits ES workers. -The previous production build passed at `38001079`: it emits the SDK Worker, -`runtime-lab.wasm`, `sqlite3.wasm` and `sqlite3.js` once each, and the wasm -matches the SDK's asset pins. Live LAB registration and readiness have not been -exercised. Old local data and -pending writes need no cutover handoff for any app; ongoing new-SDK recovery -remains mandatory. - -The original DEV/LAB intake `sdk-dev-3f417b6d` is installed through a -normal file dependency. It bundles the original beta129 protocol archive -(`c280cd194ffd`); Planner retains its direct `d765e675c3da` protocol dependency -for legacy packages. This archive has no vendor warnings; the earlier unused -protocol exception is not carried forward. Both original archives remain immutable. There are no aliases, API casts, -private source copies or package rewrites. This intake permits source typechecking, -not release or native qualification. UI lifecycle stand-ins and MemoryReplica -rendering tests do not qualify runtime, native initialization, registration or -end-to-end operation. No deployment or LAB pin change is enabled. - -The current cut adds SDK-owned lazy Worker startup, pairing only from a sign-in -gesture, native-return opening and refused/expired sign-in recovery. SDK refusal -copy is rendered unchanged; Sign in stays enabled while authorizing and invokes -SDK `approve()`. Planner supplies no pairing or renewal engine. - -## Artifact and checks - -The SDK tarball and its source commit/SHA256 are recorded in -`vendor/mdbase-next-sdk.json`. It is supplied by the release workstream, not npm. -The current pin is the **development intake, not release-qualified**, from exact -SDK source `3f417b6d941a42444f17568d7489071d23fcb141`, original `4c` cut: -4346749-byte SDK archive, SHA256 -`0c5f04c6adad67d747055ebe12963403e374aefde0999861d759f35e4e118ba6` -(live `MdbaseConnect` entry: registration, storage, Worker and runtime, with -plain-Vite-emittable Worker and native assets since `d53d7d76`, -`snapshot.selection` and `approve`/`reauthorize`/`chooseAnother`; packages the -native runtime/SQLite wasm, `describe({ definitions })`, collection selection -and `files.grant`; adds contract-scoped `getContract` and contract -create/update/delete/rename, `files.watch` and `createType` in `df091839`; -MemoryReplica serves contract-scoped records from declared `MemoryContract`s in -`38001079`; `n1` adds atomic contract body/field updates and promotion, explicit -fixture digests; `4b` delivers public error/status helpers and portable/native-origin -sign-in APIs; `6a` includes registration-refusal guidance, Private-collection -availability reasons and collection deletion APIs; `993` adds owner-binding fixes -and public setup assessment, with the original 3555291-byte native runtime matching -`9f90af73e1591cafe9c35bdd42f18ae49d5a91a0186e78695e841fecabcd833a`; -`41c` adds per-type readiness options, SDK user-facing title/message/action labels, -log-service CORS support and a thin-client entry). -The direct legacy protocol beta129 dependency archive (source `d765e675c3da`) is -70702 bytes with SHA256 -`2769616d8ba1a9cbb3dd689fa1ca0b1e2b42e5442634635f2cd4a87f5ec9020f`; it replaces -the earlier protocol718 `dfc5db0d` archive, including as the pnpm override for -the legacy Connect packages. SHA256/SHA512 were checked against the release -manifest. Relative to `e519009a`, the entry adds `assess()`, `configure()` and -`MdbaseApplicationConnection.displayName`, and the `@mdbase-dev/sdk/testing` -`createApplicationFixture` seam. -This dependency change does not by itself qualify native sign-in, READY, -deployment or a change to the held LAB SDK. - -The previous original release-qualified `b9965703` archive is retained, including SDK837's -host/caller lifetime composition fix and SDK832's optional bounded -`problemDetail`, plus the earlier public factory and provider-receipt helpers. -Release qualified all 278 archive members, 1678 SDK tests and five browser-bundle -checks (with three existing WASM-gated skips); the original 750928-byte archive -is retained unchanged. Planner consumes SDK snapshots directly and does not -introduce a diagnostic parser, raw-error logging or retry authority. This pin -activates no new factory or per-app native composition. -The new entry source port does not change Planner's native read/write implementation. -Archive SHA256/SHA512 were verified. This is source/package qualification, not -per-app trust/origin/runtime/session/operation acceptance or a published release. -The earlier SDK807 `5ba1314b` archive remains held and must not be reused; no -browser/session path is enabled by this dependency update. - -Task edits are written through the `tasknotes.task` contract -(`update(record, {contract, patch, unset, ifRevision})`): Planner sends contract -fields and Core maps them to the type's record fields. Task reads still map the -record fields client-side. - -Unit tests use the SDK's MemoryReplica for record/receipt frames, with synthetic -native contract metadata, a declared `tasknotes.task` `MemoryContract` for -contract writes, and mocked AppBases results. SDK811's shared synthetic -parser now handles `create.document`; the source-create test no longer installs -an app-side fixture bridge and still asserts the exact document submission. These tests are not LAB, native renderer, -source-parser, sign-in, or end-to-end evidence. Run `pnpm verify` and -`pnpm test:e2e` for the existing application checks. - -## Public SDK sign-in UI - -The existing source-only `createNativeSignIn(options)` binding uses the pinned -SDK's actual `AppWebSignInSession`. Its lower-level explicit release, identity, -origin, runtime, locks and signal inputs support conformance checks, not the -planned app integration. They will move behind the shared application entry; -apps must not add new runtime, Worker, SQL or setup glue to complete this binding. -The existing screen accepts its binding and an `onSignedOut` callback through -`ConnectionGate`'s optional `nativeSignIn` prop. The owner closes the borrowed SDK -session on teardown and creates a fresh instance only after confirmed close or -explicit sign-in. - -The UI reserves a named 620×760 Connect popup synchronously before SDK -start/expiry renewal, using the same app-owned pattern as TaskNotes. The trusted -Connect popup keeps its opener for parent-owned cross-origin closure; the tab -fallback detaches its opener before navigation (noopener semantics). No secrets, -postMessage approval callback or storage callback use the opener. The SDK -supplies the validated navigation URI and detects approval. The UI retains its -original handle after handoff and closes it on completed or terminal public SDK -statuses, never while authorizing additional consent, even if already paired. -Only the owned popup is cleaned up on view teardown, not the borrowed session. -A detached fallback tab is not claimed parent-closeable after crossing origins. -It renders actual -SDK status/email, tab reopening, blocked-popup and interrupted states. Local -**Sign out** waits for SDK `close()`; it neither revokes membership nor erases -protected sign-in stores. **Forget collection** calls the SDK's selected-ID -`forget` action. Failed or retired actions cannot publish another owner's state. - -The collection callback explicitly refuses with -**Opening collections isn't available in this build yet.** It does not return a -fake connection, setup result or saved state. No setup, offline-sync or reminder -promise is displayed without its real capability. A generic SDK refusal does -not prove a user decline, so the UI does not invent that state. - -Focused presentation and React forwarding tests use labelled SDK stand-ins, not -native sign-in/LAB qualification. The public application entry, authenticated SDK -asset delivery, registered identity and current setup/people composition remain -required before a native runnable URL exists. The future entry must retain this -screen's popup, selection and lifetime behavior without per-app infrastructure. diff --git a/docs/releases/unreleased.md b/docs/releases/unreleased.md index 0b890d4..a8dcf65 100644 --- a/docs/releases/unreleased.md +++ b/docs/releases/unreleased.md @@ -13,3 +13,5 @@ - Drag an unscheduled task by its title onto a date in the timeline or its header. Drops outside the timeline explain why the schedule stayed unchanged; clicking a title still opens the task editor. - Keep Save view available at phone widths as a labelled icon button, instead of hiding it. + +- Open collections through the published mdbase SDK and shared collection screens in every deployment, replacing the retired legacy Connect session. The legacy pending-change recovery banner is gone; unconfirmed writes are kept and settled by the SDK. diff --git a/package.json b/package.json index fc4034e..76d66f1 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "contracts:sync": "node scripts/sync-canonical-task-pack.mjs", "manifest": "node scripts/write-manifest.mjs", "manifest:dev": "node scripts/write-manifest.mjs --development", - "dev": "pnpm manifest:dev && vite", - "dev:e2e": "pnpm manifest:dev && vite --mode e2e", + "dev": "pnpm manifest:dev && VITE_MDBASE_ENV=lab vite", + "dev:e2e": "pnpm manifest:dev && VITE_MDBASE_ENV=lab vite --mode e2e", "deploy:dev": "node scripts/deploy-pages-dev.mjs", "deploy:lab": "MDBASE_ENV=lab node scripts/deploy-pages-dev.mjs", "deploy:production": "node scripts/deploy-pages-production.mjs", @@ -29,9 +29,8 @@ "dependencies": { "@fontsource/atkinson-hyperlegible": "5.3.0", "@fontsource/azeret-mono": "5.3.0", - "@mdbase-dev/connect": "file:vendor/mdbase-dev-connect-0.1.0-beta.96-56ed32ffde05.tgz", - "@mdbase-dev/sdk": "file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz", - "@mdbase-dev/ui": "file:vendor/ui-dev-9553ad66.tgz", + "@mdbase-dev/sdk": "0.1.0-beta.163", + "@mdbase-dev/ui": "0.1.0-beta.160", "@tasknotes/calendar": "0.1.2", "@tasknotes/model": "0.3.0-rc.17", "lucide-react": "1.25.0", @@ -41,7 +40,6 @@ }, "devDependencies": { "@eslint/js": "10.0.1", - "@mdbase-dev/connect-protocol": "file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz", "@playwright/test": "1.61.1", "@testing-library/jest-dom": "7.0.0", "@testing-library/react": "16.3.2", @@ -59,11 +57,5 @@ "typescript-eslint": "8.65.0", "vite": "8.1.5", "vitest": "4.1.10" - }, - "pnpm": { - "overrides": { - "@mdbase-dev/connect-protocol": "file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz", - "@mdbase-dev/connect-sync": "file:vendor/mdbase-dev-connect-sync-0.1.0-beta.96-56ed32ffde05.tgz" - } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87abd47..0b45ba5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,10 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - "@mdbase-dev/connect-protocol": file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz - "@mdbase-dev/connect-sync": file:vendor/mdbase-dev-connect-sync-0.1.0-beta.96-56ed32ffde05.tgz - importers: .: dependencies: @@ -17,15 +13,12 @@ importers: "@fontsource/azeret-mono": specifier: 5.3.0 version: 5.3.0 - "@mdbase-dev/connect": - specifier: file:vendor/mdbase-dev-connect-0.1.0-beta.96-56ed32ffde05.tgz - version: file:vendor/mdbase-dev-connect-0.1.0-beta.96-56ed32ffde05.tgz "@mdbase-dev/sdk": - specifier: file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz - version: file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz + specifier: 0.1.0-beta.163 + version: 0.1.0-beta.163 "@mdbase-dev/ui": - specifier: file:vendor/ui-dev-9553ad66.tgz - version: file:vendor/ui-dev-9553ad66.tgz(@mdbase-dev/sdk@file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + specifier: 0.1.0-beta.160 + version: 0.1.0-beta.160(@lingui/core@6.9.0(@babel/core@7.29.7))(@lingui/react@6.9.0(@babel/core@7.29.7)(react@19.2.8))(@mdbase-dev/i18n@0.1.0-beta.160(@lingui/core@6.9.0(@babel/core@7.29.7))(@lingui/react@6.9.0(@babel/core@7.29.7)(react@19.2.8))(react@19.2.8)(rolldown@1.1.5))(@mdbase-dev/sdk@0.1.0-beta.163)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) "@tasknotes/calendar": specifier: 0.1.2 version: 0.1.2(react@19.2.8) @@ -47,10 +40,7 @@ importers: devDependencies: "@eslint/js": specifier: 10.0.1 - version: 10.0.1(eslint@10.7.0) - "@mdbase-dev/connect-protocol": - specifier: file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz - version: file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz + version: 10.0.1(eslint@10.7.0(jiti@2.7.0)) "@playwright/test": specifier: 1.61.1 version: 1.61.1 @@ -71,16 +61,16 @@ importers: version: 19.2.3(@types/react@19.2.17) "@vitejs/plugin-react": specifier: 6.0.3 - version: 6.0.3(vite@8.1.5(@types/node@26.2.0)(yaml@2.8.2)) + version: 6.0.3(vite@8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2)) eslint: specifier: 10.7.0 - version: 10.7.0 + version: 10.7.0(jiti@2.7.0) eslint-plugin-react-hooks: specifier: 7.1.1 - version: 7.1.1(eslint@10.7.0) + version: 7.1.1(eslint@10.7.0(jiti@2.7.0)) eslint-plugin-react-refresh: specifier: 0.5.3 - version: 0.5.3(eslint@10.7.0) + version: 0.5.3(eslint@10.7.0(jiti@2.7.0)) globals: specifier: 17.7.0 version: 17.7.0 @@ -95,13 +85,13 @@ importers: version: 6.0.3 typescript-eslint: specifier: 8.65.0 - version: 8.65.0(eslint@10.7.0)(typescript@6.0.3) + version: 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) vite: specifier: 8.1.5 - version: 8.1.5(@types/node@26.2.0)(yaml@2.8.2) + version: 8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2) vitest: specifier: 4.1.10 - version: 4.1.10(@types/node@26.2.0)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.1.5(@types/node@26.2.0)(yaml@2.8.2)) + version: 4.1.10(@types/node@26.2.0)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2)) packages: "@adobe/css-tools@4.5.0": @@ -266,6 +256,13 @@ packages: } hasBin: true + "@colors/colors@1.5.0": + resolution: + { + integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, + } + engines: { node: ">=0.1.90" } + "@csstools/color-helpers@6.1.1": resolution: { @@ -413,12 +410,6 @@ packages: "@noble/hashes": optional: true - "@fontsource-variable/atkinson-hyperlegible-next@5.3.0": - resolution: - { - integrity: sha512-UnLzNUy1RWoLDfydAatP4NaRm99X/WbQEph7q/PymJnCFIOOwKOx3pBmp5ys8trO0dM+CKGuJgeZP9fAR4mQpA==, - } - "@fontsource/atkinson-hyperlegible@5.3.0": resolution: { @@ -431,6 +422,12 @@ packages: integrity: sha512-Ag/+gN67fUry7i1yLlTnk16Rr1nnTQI2YefA/G7tYcCSr/+TC/ayMrE8Km7O0OKRgI/B5Mfel46Gq9IGY50Uaw==, } + "@fontsource/ibm-plex-sans@5.3.0": + resolution: + { + integrity: sha512-CbE4CbbEEZJX860XyUiRpsksXIQR8Rp2XDva2VO53NJox9tVNtusrysd2x5YkUEY3ErQ66W1IiiQL8/wihhw5w==, + } + "@humanfs/core@0.19.2": resolution: { @@ -466,6 +463,20 @@ packages: } engines: { node: ">=18.18" } + "@jest/schemas@29.6.3": + resolution: + { + integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + "@jest/types@29.6.3": + resolution: + { + integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + "@jridgewell/gen-mapping@0.3.13": resolution: { @@ -497,42 +508,207 @@ packages: integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, } - "@mdbase-dev/connect-protocol@file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz": + "@lingui/babel-plugin-extract-messages@6.9.0": + resolution: + { + integrity: sha512-udKlLtGtJ9TjVjqtogIage/Z3merz5Ko5u72uIaHMZ9aPFjSNDnrsglWEfMLWTeSxmkdOrASDpn8YuBMLbhvPA==, + } + engines: { node: ">=22.19.0" } + + "@lingui/babel-plugin-lingui-macro@6.9.0": + resolution: + { + integrity: sha512-AwbD2JKdy3L0Vppv5X8xPZJzgY4W9Y+DcH5Tzb7oZA6o96N+VVeJU9xsLFbh5UGIQAwLeY7BZKDH88qmWj1gUA==, + } + engines: { node: ">=22.19.0" } + peerDependencies: + "@babel/core": ^7.20.12 || ^8.0.0 + peerDependenciesMeta: + "@babel/core": + optional: true + + "@lingui/cli@6.9.0": + resolution: + { + integrity: sha512-/fO982w4GsMsxs5kTf/MnvkpD5/hm3c5uFaPmTWilvf5lnaJbjA3aBaBYliRocYDhvD71xMvLqcsfITB4wC4NA==, + } + engines: { node: ">=22.19.0" } + hasBin: true + peerDependencies: + esbuild: ^0.28.1 + rolldown: ^1.0.0 + peerDependenciesMeta: + esbuild: + optional: true + rolldown: + optional: true + + "@lingui/conf@6.9.0": + resolution: + { + integrity: sha512-1fwYejoGLeoy5P1/mop0WP35fDRXkB7fUFxyPV5E+vBbgOXtw+QDRt1y9WtyqjI+dz9zQtNYmND3+tBSeAADfw==, + } + engines: { node: ">=22.19.0" } + + "@lingui/core@6.9.0": + resolution: + { + integrity: sha512-woQCOsmbG3LxhqTyECcpxn6t3V5tQOgYOG7LL424a/dmpKttyqtJ1/qq3/U7H+ZG+ewFHh56UvQrXSJVnjCf2g==, + } + engines: { node: ">=22.19.0" } + peerDependencies: + babel-plugin-macros: 2 || 3 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + "@lingui/format-po@6.9.0": + resolution: + { + integrity: sha512-nfeAxAj3An1zq0F8k01fvw+cMgqU4KAIDemylwNnTI+Mamo1x6WUo+vtpaJQjtDDTPBMVKkMoGx9G9fpy5xrtQ==, + } + engines: { node: ">=22.19.0" } + + "@lingui/message-utils@6.9.0": + resolution: + { + integrity: sha512-LaFvx9Vc/lx4EPbbDaiDtyumDM4aAglRA3yvFQsJkhs1UWrCgDaaUBWhkXKYcmVe9gb7qeGYCqmGWOL84N9xDw==, + } + engines: { node: ">=22.19.0" } + + "@lingui/native-tools-android-arm64@0.1.6": + resolution: + { + integrity: sha512-kAwVqJlKv868dGJOhqXXqHwhI3bOlvdHSZz8rozlEEidvBuw6Srr0Es5hwisLlwybzcTYstjOeVGxmvu3QAV4g==, + } + engines: { node: ">=22.19.0" } + cpu: [arm64] + os: [android] + + "@lingui/native-tools-darwin-arm64@0.1.6": + resolution: + { + integrity: sha512-0KEoSob+pYS1jyRB+2bJp78L2L2i/VODFIk5Gs8LmKV58xZ+uN8MOhf1dYBBsi6+0tiqPartSCk3WA4i+lKPUQ==, + } + engines: { node: ">=22.19.0" } + cpu: [arm64] + os: [darwin] + + "@lingui/native-tools-darwin-x64@0.1.6": + resolution: + { + integrity: sha512-e7liIjkzbJ8w3F9PHzZPmpYpeiv7XO4Np9J1tEpUamKYEdYBNt4AKr/UmWAQ7AjxbZSV9N8lj1ulUl3pdFUuaQ==, + } + engines: { node: ">=22.19.0" } + cpu: [x64] + os: [darwin] + + "@lingui/native-tools-linux-arm64-gnu@0.1.6": resolution: { - integrity: sha512-PFG1qmAzmcyJjmZLmGz4eoOOZRDgvTvDZATf6IGBccNHRc1eGZtMm7ZUA7gNAxuO5KerrN6mDIB9qQnpojyOAg==, - tarball: file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz, + integrity: sha512-JKu/8CvLsYUiRbRSnuzmrQk24tfuizgnDYlMxE5/awUUrRE+GlpaMP76PhJpkl+tV0v7t6zVe3+I5lbq7LaqVw==, } - version: 0.1.0-beta.129 + engines: { node: ">=22.19.0" } + cpu: [arm64] + os: [linux] - "@mdbase-dev/connect@file:vendor/mdbase-dev-connect-0.1.0-beta.96-56ed32ffde05.tgz": + "@lingui/native-tools-linux-arm64-musl@0.1.6": resolution: { - integrity: sha512-7k2TL4IVu4IyWSs+1DuSDrY/Vw0vAHlpgL3Zq9R/Bb1I61TMwTTQ2p/aYx400FT69qQGO1CA6SHs7oHNroHh5w==, - tarball: file:vendor/mdbase-dev-connect-0.1.0-beta.96-56ed32ffde05.tgz, + integrity: sha512-Aeexf/Ta0nZDZz3cDMAZ95GmFZ2lOxMwf1FK/ShK+U4WcNVSn3aI2KeuOeaLUfot9ZT8AiIIELPV4gO6b/GJgg==, } - version: 0.1.0-beta.96 + engines: { node: ">=22.19.0" } + cpu: [arm64] + os: [linux] - "@mdbase-dev/sdk@file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz": + "@lingui/native-tools-linux-x64-gnu@0.1.6": resolution: { - integrity: sha512-4OZ2+PnQOIoHOq1M5ctvKt3DNYsdPHGcGcKiP1nPD9v6udkLXnDNN3qc0TQGUfmtazKNIEQpSfogffzcZlHJeA==, - tarball: file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz, + integrity: sha512-SwX1dLcQE9pVcXwIxSGTE2+zBmaO6NbjD9XPqCiyoO8RFTkUTLjdnRDGHxFlFpIGMlVArQz5mMQ6coyUFl+AiA==, + } + engines: { node: ">=22.19.0" } + cpu: [x64] + os: [linux] + + "@lingui/native-tools-linux-x64-musl@0.1.6": + resolution: + { + integrity: sha512-aWI8e10I6i7K9BG77LJDHREozx/VT8j8onf0wxd43ACor2S1VwS0ZNlIfpNWQTWovmU/CUliGO/VWQq2jGa+Sg==, + } + engines: { node: ">=22.19.0" } + cpu: [x64] + os: [linux] + + "@lingui/native-tools-win32-arm64-msvc@0.1.6": + resolution: + { + integrity: sha512-M2TZP4gKgLaLbiboXIrY5+8GsaQLHvZ8E6cUFgAuSaBa8AMa73+pZO1eay/St9t8yDh6EL6hOlqN0HedUHMwyQ==, + } + engines: { node: ">=22.19.0" } + cpu: [arm64] + os: [win32] + + "@lingui/native-tools-win32-x64-msvc@0.1.6": + resolution: + { + integrity: sha512-5KvuSTFtDB8UGHS3Rxd+juYZgc51isQSx0hPukDObre6UxZGuUVCBmJ9YwoGVzXedViiNGmsQvrnCTBbVqI75g==, + } + engines: { node: ">=22.19.0" } + cpu: [x64] + os: [win32] + + "@lingui/native-tools@0.1.6": + resolution: + { + integrity: sha512-xsn2R9YvWL9WuemM0ivSlXy14ORlfvPYLxFP3kbcFU3Pg5T9aRnmYOTfwszwGnjt4QsORO1mHHZT6k/Wr7V9pA==, + } + engines: { node: ">=22.19.0" } + peerDependencies: + "@lingui/conf": ^6.0.1 + + "@lingui/react@6.9.0": + resolution: + { + integrity: sha512-0oSy/GpQUzMrylviy/IiDg6b0DH4HwGusSD8ht2dsDCdunH+wX8bfJ/njo7qtjyaawTjEbdyLCUzlZc2Gd2kVQ==, + } + engines: { node: ">=22.19.0" } + peerDependencies: + babel-plugin-macros: 2 || 3 + react: ^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + "@mdbase-dev/i18n@0.1.0-beta.160": + resolution: + { + integrity: sha512-Z21y+yx+ZTvZdO80iRVwZLKpGd2tA8WBzJIgiY+4/cYKbAwyXv9G4MSxoiCjxY2vi4xCHk0/gsj4RNpiNxCTeQ==, + } + hasBin: true + peerDependencies: + "@lingui/core": 6.9.0 + "@lingui/react": 6.9.0 + react: ^19.2.0 + + "@mdbase-dev/sdk@0.1.0-beta.163": + resolution: + { + integrity: sha512-tUY8zRvW0idcPDVR8HwVhEgZPZ9wdWIPQd5uOSBuUuLj9JQxUUpgQaAUImUl8kzPgtgBJmG267CiQ6cPX/QTPw==, } - version: 0.0.0 engines: { node: ">=20" } bundledDependencies: - "@mdbase-dev/connect-protocol" - "@mdbase-dev/ui@file:vendor/ui-dev-9553ad66.tgz": + "@mdbase-dev/ui@0.1.0-beta.160": resolution: { - integrity: sha512-PqMWvJ2ZbNaLxWTum2m7vmcCbOGdM5qldnphPwqyGBo/mcpvjfk5tHkil/vXLc+LuG7HEyZOLh+tZyUXtMv7QQ==, - tarball: file:vendor/ui-dev-9553ad66.tgz, + integrity: sha512-jTVegUfNv9ihSJNGP/No+kh8cxH3RZIPJrNh+BfxWOQNpovZ88Z7LvFBC15lw9x9Czp14JR8+OqZArDW5yC27w==, } - version: 0.1.0-beta.129 peerDependencies: "@codemirror/view": ^6.0.0 + "@lingui/core": 6.9.0 + "@lingui/react": 6.9.0 + "@mdbase-dev/i18n": "*" "@mdbase-dev/sdk": "*" react: ^19.2.0 react-dom: ^19.2.0 @@ -544,6 +720,18 @@ packages: react-dom: optional: true + "@messageformat/date-skeleton@1.1.0": + resolution: + { + integrity: sha512-rmGAfB1tIPER+gh3p/RgA+PVeRE/gxuQ2w4snFWPF5xtb5mbWR7Cbw7wCOftcUypbD6HVoxrVdyyghPm3WzP5A==, + } + + "@messageformat/parser@5.2.0": + resolution: + { + integrity: sha512-sc3hNZ0JtC32GhL5nUgPbh2ilcwji4G+Vmj/vhMd8hZaH//NDOvkfwKK1mFnRWG5h2DpksN8xr/HB5/+vMg3iw==, + } + "@napi-rs/wasm-runtime@1.2.3": resolution: { @@ -729,12 +917,30 @@ packages: integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==, } + "@sinclair/typebox@0.27.12": + resolution: + { + integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==, + } + "@standard-schema/spec@1.1.0": resolution: { integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==, } + "@swc/counter@0.1.3": + resolution: + { + integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==, + } + + "@swc/types@0.1.28": + resolution: + { + integrity: sha512-V6Mnml8v09QALx6K0elJ7o9K/MkVDtW3t6L+7Ou/JcWtb3xwId2AH4FeOceySd2JaO87IMw4+6vSZxLm34LPbw==, + } + "@tasknotes/calendar@0.1.2": resolution: { @@ -823,6 +1029,24 @@ packages: integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==, } + "@types/istanbul-lib-coverage@2.0.6": + resolution: + { + integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==, + } + + "@types/istanbul-lib-report@3.0.3": + resolution: + { + integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==, + } + + "@types/istanbul-reports@3.0.4": + resolution: + { + integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==, + } + "@types/json-schema@7.0.15": resolution: { @@ -849,6 +1073,18 @@ packages: integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==, } + "@types/yargs-parser@21.0.3": + resolution: + { + integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==, + } + + "@types/yargs@17.0.35": + resolution: + { + integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==, + } + "@typescript-eslint/eslint-plugin@8.65.0": resolution: { @@ -1020,33 +1256,30 @@ packages: engines: { node: ">=0.4.0" } hasBin: true - ajv-formats@3.0.1: + ajv@6.15.0: resolution: { - integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==, + integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==, } - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv@6.15.0: + ansi-regex@5.0.1: resolution: { - integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==, + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, } + engines: { node: ">=8" } - ajv@8.20.0: + ansi-regex@6.5.0: resolution: { - integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==, + integrity: sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==, } + engines: { node: ">=12" } - ansi-regex@5.0.1: + ansi-styles@4.3.0: resolution: { - integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, } engines: { node: ">=8" } @@ -1105,6 +1338,13 @@ packages: } engines: { node: 20 || >=22 } + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: ">=8" } + browserslist@4.28.8: resolution: { @@ -1113,6 +1353,13 @@ packages: engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } hasBin: true + camelcase@6.3.0: + resolution: + { + integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, + } + engines: { node: ">=10" } + caniuse-lite@1.0.30001809: resolution: { @@ -1126,6 +1373,75 @@ packages: } engines: { node: ">=18" } + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: ">=10" } + + chalk@5.6.2: + resolution: + { + integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==, + } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + chokidar@5.0.0: + resolution: + { + integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==, + } + engines: { node: ">= 20.19.0" } + + cli-cursor@5.0.0: + resolution: + { + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, + } + engines: { node: ">=18" } + + cli-spinners@3.4.0: + resolution: + { + integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==, + } + engines: { node: ">=18.20" } + + cli-table3@0.6.5: + resolution: + { + integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==, + } + engines: { node: 10.* || >= 12.* } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + commander@13.1.0: + resolution: + { + integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==, + } + engines: { node: ">=18" } + + commander@14.0.3: + resolution: + { + integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==, + } + engines: { node: ">=20" } + convert-source-map@2.0.0: resolution: { @@ -1221,6 +1537,12 @@ packages: integrity: sha512-4R8XgQOdfxexCd/u63lRm6wCHjECwI45MV9wxAs2ggtfWe2hwlo1ql97jKsju2IcJ+jFSTwBssyYoiWhh7mauQ==, } + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + entities@8.0.0: resolution: { @@ -1365,12 +1687,6 @@ packages: integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, } - fast-uri@3.1.5: - resolution: - { - integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==, - } - fdir@6.5.0: resolution: { @@ -1390,6 +1706,13 @@ packages: } engines: { node: ">=16.0.0" } + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: ">=8" } + find-up@5.0.0: resolution: { @@ -1433,6 +1756,13 @@ packages: } engines: { node: ">=6.9.0" } + get-east-asian-width@1.7.0: + resolution: + { + integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==, + } + engines: { node: ">=18" } + glob-parent@6.0.2: resolution: { @@ -1447,6 +1777,13 @@ packages: } engines: { node: ">=18" } + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: ">=8" } + hermes-estree@0.25.1: resolution: { @@ -1501,6 +1838,13 @@ packages: } engines: { node: ">=0.10.0" } + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: ">=8" } + is-glob@4.0.3: resolution: { @@ -1508,18 +1852,66 @@ packages: } engines: { node: ">=0.10.0" } + is-interactive@2.0.0: + resolution: + { + integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==, + } + engines: { node: ">=12" } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: ">=0.12.0" } + is-potential-custom-element-name@1.0.1: resolution: { integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==, } + is-unicode-supported@2.1.0: + resolution: + { + integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==, + } + engines: { node: ">=18" } + isexe@2.0.0: resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, } + jest-get-type@29.6.3: + resolution: + { + integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-validate@29.7.0: + resolution: + { + integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jiti@2.7.0: + resolution: + { + integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==, + } + hasBin: true + + js-sha256@0.10.1: + resolution: + { + integrity: sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==, + } + js-tokens@4.0.0: resolution: { @@ -1558,12 +1950,6 @@ packages: 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: { @@ -1584,6 +1970,13 @@ packages: integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, } + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, + } + engines: { node: ">=6" } + levn@0.4.1: resolution: { @@ -1697,6 +2090,13 @@ packages: } engines: { node: ">= 12.0.0" } + lilconfig@3.1.4: + resolution: + { + integrity: sha512-zJZyBmQhArDRefuWTypZxwDpFDIYmrj8ypSBvwGD5P13Y95s6DjzKgu4DVdGbeQa5PDEsAOCAPNk4X+QOR006w==, + } + engines: { node: ">=14" } + locate-path@6.0.0: resolution: { @@ -1704,6 +2104,13 @@ packages: } engines: { node: ">=10" } + log-symbols@7.0.1: + resolution: + { + integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==, + } + engines: { node: ">=18" } + lru-cache@11.5.2: resolution: { @@ -1744,6 +2151,20 @@ packages: integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==, } + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: ">=8.6" } + + mimic-function@5.0.1: + resolution: + { + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, + } + engines: { node: ">=18" } + min-indent@1.0.1: resolution: { @@ -1758,6 +2179,12 @@ packages: } engines: { node: 18 || 20 || >=22 } + moo@0.5.3: + resolution: + { + integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==, + } + ms@2.1.3: resolution: { @@ -1785,6 +2212,13 @@ packages: } engines: { node: ">=18" } + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, + } + engines: { node: ">=0.10.0" } + obug@2.1.4: resolution: { @@ -1792,6 +2226,13 @@ packages: } engines: { node: ">=12.20.0" } + onetime@7.0.0: + resolution: + { + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, + } + engines: { node: ">=18" } + optionator@0.9.4: resolution: { @@ -1799,6 +2240,13 @@ packages: } engines: { node: ">= 0.8.0" } + ora@9.4.1: + resolution: + { + integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==, + } + engines: { node: ">=20" } + p-limit@3.1.0: resolution: { @@ -1845,6 +2293,13 @@ packages: integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, } + picomatch@2.3.2: + resolution: + { + integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==, + } + engines: { node: ">=8.6" } + picomatch@4.0.5: resolution: { @@ -1865,8 +2320,15 @@ packages: { integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==, } - engines: { node: ">=18" } - hasBin: true + engines: { node: ">=18" } + hasBin: true + + pofile-ts@4.0.3: + resolution: + { + integrity: sha512-sz1pnjgEfPyZ+QvaeX3NtCmbYnEvG01LZRLoN/uXoLtPZtxCIH5IctL7yXXc0fFyk/fqV6K8g3hlNfr6IJwupA==, + } + engines: { node: ">=20" } postcss@8.5.26: resolution: @@ -1897,6 +2359,21 @@ packages: } engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + pretty-format@29.7.0: + resolution: + { + integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + pseudolocale@3.1.0: + resolution: + { + integrity: sha512-jBidehQu5WO8zbFUod0ETJYoVPxZ0ixsdI9B8ilkE0eA+YTydxHA7ujNdU4YhTexD5yH5Griv9r5CuXx+nwFUA==, + } + engines: { node: ">=24.0.0" } + hasBin: true + punycode@2.3.1: resolution: { @@ -1918,6 +2395,12 @@ packages: integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, } + react-is@18.3.1: + resolution: + { + integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, + } + react@19.2.8: resolution: { @@ -1925,6 +2408,13 @@ packages: } engines: { node: ">=0.10.0" } + readdirp@5.1.1: + resolution: + { + integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==, + } + engines: { node: ">= 20.19.0" } + redent@3.0.0: resolution: { @@ -1939,6 +2429,13 @@ packages: } engines: { node: ">=0.10.0" } + restore-cursor@5.1.0: + resolution: + { + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, + } + engines: { node: ">=18" } + rolldown@1.1.5: resolution: { @@ -2001,6 +2498,13 @@ packages: integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, } + signal-exit@4.1.0: + resolution: + { + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, + } + engines: { node: ">=14" } + source-map-js@1.2.1: resolution: { @@ -2008,6 +2512,13 @@ packages: } engines: { node: ">=0.10.0" } + source-map@0.7.6: + resolution: + { + integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==, + } + engines: { node: ">= 12" } + stackback@0.0.2: resolution: { @@ -2020,6 +2531,41 @@ packages: integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==, } + stdin-discarder@0.3.2: + resolution: + { + integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==, + } + engines: { node: ">=18" } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: ">=8" } + + string-width@8.3.0: + resolution: + { + integrity: sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==, + } + engines: { node: ">=20" } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: ">=8" } + + strip-ansi@7.2.0: + resolution: + { + integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==, + } + engines: { node: ">=12" } + strip-indent@3.0.0: resolution: { @@ -2027,6 +2573,13 @@ packages: } engines: { node: ">=8" } + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: ">=8" } + symbol-tree@3.2.4: resolution: { @@ -2053,6 +2606,13 @@ packages: } engines: { node: ">=12.0.0" } + tinypool@2.2.0: + resolution: + { + integrity: sha512-jBrmx4lYmaC9k/mgPbylxs7kBUxHtD8256up+HjLaDFfXScKJQyil+SWXSvhAtT7XHo+yTVlpB81PGmHP8oLSQ==, + } + engines: { node: ^20.0.0 || >=22.0.0 } + tinyrainbow@3.1.1: resolution: { @@ -2073,6 +2633,13 @@ packages: } hasBin: true + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: ">=8.0" } + tough-cookie@6.0.2: resolution: { @@ -2155,6 +2722,14 @@ packages: integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, } + use-sync-external-store@1.7.0: + resolution: + { + integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==, + } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vite@8.1.5: resolution: { @@ -2323,20 +2898,19 @@ packages: engines: { node: ">= 14.6" } hasBin: true - yaml@2.9.1: + yocto-queue@0.1.0: resolution: { - integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==, + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, } - engines: { node: ">= 14.6" } - hasBin: true + engines: { node: ">=10" } - yocto-queue@0.1.0: + yoctocolors@2.2.0: resolution: { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==, } - engines: { node: ">=10" } + engines: { node: ">=18" } zod-validation-error@4.0.2: resolution: @@ -2488,6 +3062,9 @@ snapshots: dependencies: css-tree: 3.2.1 + "@colors/colors@1.5.0": + optional: true + "@csstools/color-helpers@6.1.1": {} "@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)": @@ -2528,9 +3105,9 @@ snapshots: tslib: 2.8.1 optional: true - "@eslint-community/eslint-utils@4.10.1(eslint@10.7.0)": + "@eslint-community/eslint-utils@4.10.1(eslint@10.7.0(jiti@2.7.0))": dependencies: - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 "@eslint-community/regexpp@4.12.2": {} @@ -2551,9 +3128,9 @@ snapshots: dependencies: "@types/json-schema": 7.0.15 - "@eslint/js@10.0.1(eslint@10.7.0)": + "@eslint/js@10.0.1(eslint@10.7.0(jiti@2.7.0))": optionalDependencies: - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) "@eslint/object-schema@3.0.5": {} @@ -2566,12 +3143,12 @@ snapshots: optionalDependencies: "@noble/hashes": 2.4.0 - "@fontsource-variable/atkinson-hyperlegible-next@5.3.0": {} - "@fontsource/atkinson-hyperlegible@5.3.0": {} "@fontsource/azeret-mono@5.3.0": {} + "@fontsource/ibm-plex-sans@5.3.0": {} + "@humanfs/core@0.19.2": dependencies: "@humanfs/types": 0.15.0 @@ -2588,6 +3165,19 @@ snapshots: "@humanwhocodes/retry@0.4.3": {} + "@jest/schemas@29.6.3": + dependencies: + "@sinclair/typebox": 0.27.12 + + "@jest/types@29.6.3": + dependencies: + "@jest/schemas": 29.6.3 + "@types/istanbul-lib-coverage": 2.0.6 + "@types/istanbul-reports": 3.0.4 + "@types/node": 26.2.0 + "@types/yargs": 17.0.35 + chalk: 4.1.2 + "@jridgewell/gen-mapping@0.3.13": dependencies: "@jridgewell/sourcemap-codec": 1.5.5 @@ -2607,32 +3197,165 @@ snapshots: "@jridgewell/resolve-uri": 3.1.2 "@jridgewell/sourcemap-codec": 1.5.5 - "@mdbase-dev/connect-protocol@file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz": + "@lingui/babel-plugin-extract-messages@6.9.0": dependencies: - "@noble/hashes": 2.4.0 - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - yaml: 2.9.1 + "@lingui/conf": 6.9.0 + + "@lingui/babel-plugin-lingui-macro@6.9.0(@babel/core@7.29.7)": + dependencies: + "@babel/types": 7.29.8 + "@lingui/conf": 6.9.0 + "@lingui/message-utils": 6.9.0 + optionalDependencies: + "@babel/core": 7.29.7 + + "@lingui/cli@6.9.0(rolldown@1.1.5)": + dependencies: + "@babel/core": 7.29.7 + "@babel/generator": 7.29.8 + "@babel/parser": 7.29.8 + "@babel/types": 7.29.8 + "@lingui/babel-plugin-extract-messages": 6.9.0 + "@lingui/babel-plugin-lingui-macro": 6.9.0(@babel/core@7.29.7) + "@lingui/conf": 6.9.0 + "@lingui/core": 6.9.0(@babel/core@7.29.7) + "@lingui/format-po": 6.9.0 + "@lingui/message-utils": 6.9.0 + "@lingui/native-tools": 0.1.6(@lingui/conf@6.9.0) + chokidar: 5.0.0 + cli-table3: 0.6.5 + commander: 14.0.3 + jiti: 2.7.0 + micromatch: 4.0.8 + ms: 2.1.3 + normalize-path: 3.0.0 + ora: 9.4.1 + pseudolocale: 3.1.0 + source-map: 0.7.6 + tinypool: 2.2.0 + optionalDependencies: + rolldown: 1.1.5 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + "@lingui/conf@6.9.0": + dependencies: + jest-validate: 29.7.0 + jiti: 2.7.0 + lilconfig: 3.1.4 + normalize-path: 3.0.0 + + "@lingui/core@6.9.0(@babel/core@7.29.7)": + dependencies: + "@lingui/babel-plugin-lingui-macro": 6.9.0(@babel/core@7.29.7) + "@lingui/message-utils": 6.9.0 + transitivePeerDependencies: + - "@babel/core" + + "@lingui/format-po@6.9.0": + dependencies: + "@lingui/conf": 6.9.0 + "@lingui/message-utils": 6.9.0 + pofile-ts: 4.0.3 + + "@lingui/message-utils@6.9.0": + dependencies: + "@messageformat/date-skeleton": 1.1.0 + "@messageformat/parser": 5.2.0 + js-sha256: 0.10.1 + + "@lingui/native-tools-android-arm64@0.1.6": + optional: true + + "@lingui/native-tools-darwin-arm64@0.1.6": + optional: true + + "@lingui/native-tools-darwin-x64@0.1.6": + optional: true - "@mdbase-dev/connect@file:vendor/mdbase-dev-connect-0.1.0-beta.96-56ed32ffde05.tgz": + "@lingui/native-tools-linux-arm64-gnu@0.1.6": + optional: true + + "@lingui/native-tools-linux-arm64-musl@0.1.6": + optional: true + + "@lingui/native-tools-linux-x64-gnu@0.1.6": + optional: true + + "@lingui/native-tools-linux-x64-musl@0.1.6": + optional: true + + "@lingui/native-tools-win32-arm64-msvc@0.1.6": + optional: true + + "@lingui/native-tools-win32-x64-msvc@0.1.6": + optional: true + + "@lingui/native-tools@0.1.6(@lingui/conf@6.9.0)": + dependencies: + "@lingui/conf": 6.9.0 + "@swc/types": 0.1.28 + optionalDependencies: + "@lingui/native-tools-android-arm64": 0.1.6 + "@lingui/native-tools-darwin-arm64": 0.1.6 + "@lingui/native-tools-darwin-x64": 0.1.6 + "@lingui/native-tools-linux-arm64-gnu": 0.1.6 + "@lingui/native-tools-linux-arm64-musl": 0.1.6 + "@lingui/native-tools-linux-x64-gnu": 0.1.6 + "@lingui/native-tools-linux-x64-musl": 0.1.6 + "@lingui/native-tools-win32-arm64-msvc": 0.1.6 + "@lingui/native-tools-win32-x64-msvc": 0.1.6 + + "@lingui/react@6.9.0(@babel/core@7.29.7)(react@19.2.8)": + dependencies: + "@lingui/babel-plugin-lingui-macro": 6.9.0(@babel/core@7.29.7) + "@lingui/core": 6.9.0(@babel/core@7.29.7) + react: 19.2.8 + use-sync-external-store: 1.7.0(react@19.2.8) + transitivePeerDependencies: + - "@babel/core" + + "@mdbase-dev/i18n@0.1.0-beta.160(@lingui/core@6.9.0(@babel/core@7.29.7))(@lingui/react@6.9.0(@babel/core@7.29.7)(react@19.2.8))(react@19.2.8)(rolldown@1.1.5)": dependencies: - "@mdbase-dev/connect-protocol": file:vendor/mdbase-dev-connect-protocol-0.1.0-beta.129-d765e675c3da.tgz + "@lingui/cli": 6.9.0(rolldown@1.1.5) + "@lingui/conf": 6.9.0 + "@lingui/core": 6.9.0(@babel/core@7.29.7) + "@lingui/format-po": 6.9.0 + "@lingui/message-utils": 6.9.0 + "@lingui/react": 6.9.0(@babel/core@7.29.7)(react@19.2.8) + react: 19.2.8 + typescript: 6.0.3 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild + - rolldown + - supports-color - "@mdbase-dev/sdk@file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz": + "@mdbase-dev/sdk@0.1.0-beta.163": dependencies: "@noble/ciphers": 2.4.0 "@noble/curves": 2.4.0 "@noble/hashes": 2.4.0 - "@mdbase-dev/ui@file:vendor/ui-dev-9553ad66.tgz(@mdbase-dev/sdk@file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)": + "@mdbase-dev/ui@0.1.0-beta.160(@lingui/core@6.9.0(@babel/core@7.29.7))(@lingui/react@6.9.0(@babel/core@7.29.7)(react@19.2.8))(@mdbase-dev/i18n@0.1.0-beta.160(@lingui/core@6.9.0(@babel/core@7.29.7))(@lingui/react@6.9.0(@babel/core@7.29.7)(react@19.2.8))(react@19.2.8)(rolldown@1.1.5))(@mdbase-dev/sdk@0.1.0-beta.163)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)": dependencies: - "@fontsource-variable/atkinson-hyperlegible-next": 5.3.0 "@fontsource/azeret-mono": 5.3.0 + "@fontsource/ibm-plex-sans": 5.3.0 + "@lingui/core": 6.9.0(@babel/core@7.29.7) + "@lingui/react": 6.9.0(@babel/core@7.29.7)(react@19.2.8) + "@mdbase-dev/i18n": 0.1.0-beta.160(@lingui/core@6.9.0(@babel/core@7.29.7))(@lingui/react@6.9.0(@babel/core@7.29.7)(react@19.2.8))(react@19.2.8)(rolldown@1.1.5) react: 19.2.8 optionalDependencies: - "@mdbase-dev/sdk": file:vendor/mdbase-dev-sdk-dev-0ecbef41.tgz + "@mdbase-dev/sdk": 0.1.0-beta.163 react-dom: 19.2.8(react@19.2.8) + "@messageformat/date-skeleton@1.1.0": {} + + "@messageformat/parser@5.2.0": + dependencies: + moo: 0.5.3 + "@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)": dependencies: "@emnapi/core": 1.11.1 @@ -2705,8 +3428,16 @@ snapshots: "@rolldown/pluginutils@1.0.1": {} + "@sinclair/typebox@0.27.12": {} + "@standard-schema/spec@1.1.0": {} + "@swc/counter@0.1.3": {} + + "@swc/types@0.1.28": + dependencies: + "@swc/counter": 0.1.3 + "@tasknotes/calendar@0.1.2(react@19.2.8)": optionalDependencies: react: 19.2.8 @@ -2766,6 +3497,16 @@ snapshots: "@types/estree@1.0.9": {} + "@types/istanbul-lib-coverage@2.0.6": {} + + "@types/istanbul-lib-report@3.0.3": + dependencies: + "@types/istanbul-lib-coverage": 2.0.6 + + "@types/istanbul-reports@3.0.4": + dependencies: + "@types/istanbul-lib-report": 3.0.3 + "@types/json-schema@7.0.15": {} "@types/node@26.2.0": @@ -2780,15 +3521,21 @@ snapshots: dependencies: csstype: 3.2.3 - "@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0)(typescript@6.0.3)": + "@types/yargs-parser@21.0.3": {} + + "@types/yargs@17.0.35": + dependencies: + "@types/yargs-parser": 21.0.3 + + "@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)": dependencies: "@eslint-community/regexpp": 4.12.2 - "@typescript-eslint/parser": 8.65.0(eslint@10.7.0)(typescript@6.0.3) + "@typescript-eslint/parser": 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) "@typescript-eslint/scope-manager": 8.65.0 - "@typescript-eslint/type-utils": 8.65.0(eslint@10.7.0)(typescript@6.0.3) - "@typescript-eslint/utils": 8.65.0(eslint@10.7.0)(typescript@6.0.3) + "@typescript-eslint/type-utils": 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + "@typescript-eslint/utils": 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) "@typescript-eslint/visitor-keys": 8.65.0 - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -2796,14 +3543,14 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3)": + "@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)": dependencies: "@typescript-eslint/scope-manager": 8.65.0 "@typescript-eslint/types": 8.65.0 "@typescript-eslint/typescript-estree": 8.65.0(typescript@6.0.3) "@typescript-eslint/visitor-keys": 8.65.0 debug: 4.4.3 - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2826,13 +3573,13 @@ snapshots: dependencies: typescript: 6.0.3 - "@typescript-eslint/type-utils@8.65.0(eslint@10.7.0)(typescript@6.0.3)": + "@typescript-eslint/type-utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)": dependencies: "@typescript-eslint/types": 8.65.0 "@typescript-eslint/typescript-estree": 8.65.0(typescript@6.0.3) - "@typescript-eslint/utils": 8.65.0(eslint@10.7.0)(typescript@6.0.3) + "@typescript-eslint/utils": 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) debug: 4.4.3 - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -2855,13 +3602,13 @@ snapshots: transitivePeerDependencies: - supports-color - "@typescript-eslint/utils@8.65.0(eslint@10.7.0)(typescript@6.0.3)": + "@typescript-eslint/utils@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)": dependencies: - "@eslint-community/eslint-utils": 4.10.1(eslint@10.7.0) + "@eslint-community/eslint-utils": 4.10.1(eslint@10.7.0(jiti@2.7.0)) "@typescript-eslint/scope-manager": 8.65.0 "@typescript-eslint/types": 8.65.0 "@typescript-eslint/typescript-estree": 8.65.0(typescript@6.0.3) - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -2871,10 +3618,10 @@ snapshots: "@typescript-eslint/types": 8.65.0 eslint-visitor-keys: 5.0.1 - "@vitejs/plugin-react@6.0.3(vite@8.1.5(@types/node@26.2.0)(yaml@2.8.2))": + "@vitejs/plugin-react@6.0.3(vite@8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2))": dependencies: "@rolldown/pluginutils": 1.0.1 - vite: 8.1.5(@types/node@26.2.0)(yaml@2.8.2) + vite: 8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2) "@vitest/expect@4.1.10": dependencies: @@ -2885,13 +3632,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.1 - "@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.2.0)(yaml@2.8.2))": + "@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2))": dependencies: "@vitest/spy": 4.1.10 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.5(@types/node@26.2.0)(yaml@2.8.2) + vite: 8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2) "@vitest/pretty-format@4.1.10": dependencies: @@ -2923,10 +3670,6 @@ snapshots: acorn@8.18.0: {} - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 @@ -2934,15 +3677,14 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ansi-regex@5.0.1: {} + ansi-regex@6.5.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@5.2.0: {} aria-query@5.3.0: @@ -2965,6 +3707,10 @@ snapshots: dependencies: balanced-match: 4.0.4 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + browserslist@4.28.8: dependencies: baseline-browser-mapping: 2.11.14 @@ -2973,10 +3719,45 @@ snapshots: node-releases: 2.0.53 update-browserslist-db: 1.3.1(browserslist@4.28.8) + camelcase@6.3.0: {} + caniuse-lite@1.0.30001809: {} chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + chokidar@5.0.0: + dependencies: + readdirp: 5.1.1 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@3.4.0: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + "@colors/colors": 1.5.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@13.1.0: {} + + commander@14.0.3: {} + convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -3019,6 +3800,8 @@ snapshots: electron-to-chromium@1.5.407: {} + emoji-regex@8.0.0: {} + entities@8.0.0: {} es-module-lexer@2.3.1: {} @@ -3027,20 +3810,20 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@7.1.1(eslint@10.7.0): + eslint-plugin-react-hooks@7.1.1(eslint@10.7.0(jiti@2.7.0)): dependencies: "@babel/core": 7.29.7 "@babel/parser": 7.29.8 - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) hermes-parser: 0.25.1 zod: 4.4.3 zod-validation-error: 4.0.2(zod@4.4.3) transitivePeerDependencies: - supports-color - eslint-plugin-react-refresh@0.5.3(eslint@10.7.0): + eslint-plugin-react-refresh@0.5.3(eslint@10.7.0(jiti@2.7.0)): dependencies: - eslint: 10.7.0 + eslint: 10.7.0(jiti@2.7.0) eslint-scope@9.1.2: dependencies: @@ -3053,9 +3836,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.7.0: + eslint@10.7.0(jiti@2.7.0): dependencies: - "@eslint-community/eslint-utils": 4.10.1(eslint@10.7.0) + "@eslint-community/eslint-utils": 4.10.1(eslint@10.7.0(jiti@2.7.0)) "@eslint-community/regexpp": 4.12.2 "@eslint/config-array": 0.23.5 "@eslint/config-helpers": 0.6.0 @@ -3085,6 +3868,8 @@ snapshots: minimatch: 10.2.6 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -3118,8 +3903,6 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.5: {} - fdir@6.5.0(picomatch@4.0.5): optionalDependencies: picomatch: 4.0.5 @@ -3128,6 +3911,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3148,12 +3935,16 @@ snapshots: gensync@1.0.0-beta.2: {} + get-east-asian-width@1.7.0: {} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 globals@17.7.0: {} + has-flag@4.0.0: {} + hermes-estree@0.25.1: {} hermes-parser@0.25.1: @@ -3176,14 +3967,37 @@ snapshots: is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-interactive@2.0.0: {} + + is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-unicode-supported@2.1.0: {} + isexe@2.0.0: {} + jest-get-type@29.6.3: {} + + jest-validate@29.7.0: + dependencies: + "@jest/types": 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jiti@2.7.0: {} + + js-sha256@0.10.1: {} + js-tokens@4.0.0: {} jsdom@29.1.1(@noble/hashes@2.4.0): @@ -3218,8 +4032,6 @@ snapshots: json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} json5@2.2.3: {} @@ -3228,6 +4040,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + leven@3.1.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -3282,10 +4096,17 @@ snapshots: lightningcss-win32-arm64-msvc: 1.33.0 lightningcss-win32-x64-msvc: 1.33.0 + lilconfig@3.1.4: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.2.0 + lru-cache@11.5.2: {} lru-cache@5.1.1: @@ -3304,12 +4125,21 @@ snapshots: mdn-data@2.27.1: {} + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mimic-function@5.0.1: {} + min-indent@1.0.1: {} minimatch@10.2.6: dependencies: brace-expansion: 5.0.9 + moo@0.5.3: {} + ms@2.1.3: {} nanoid@3.3.18: {} @@ -3318,8 +4148,14 @@ snapshots: node-releases@2.0.53: {} + normalize-path@3.0.0: {} + obug@2.1.4: {} + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3329,6 +4165,17 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@9.4.1: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.3.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -3349,6 +4196,8 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.5: {} playwright-core@1.61.1: {} @@ -3359,6 +4208,8 @@ snapshots: optionalDependencies: fsevents: 2.3.2 + pofile-ts@4.0.3: {} + postcss@8.5.26: dependencies: nanoid: 3.3.18 @@ -3375,6 +4226,16 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 + pretty-format@29.7.0: + dependencies: + "@jest/schemas": 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + pseudolocale@3.1.0: + dependencies: + commander: 13.1.0 + punycode@2.3.1: {} react-dom@19.2.8(react@19.2.8): @@ -3384,8 +4245,12 @@ snapshots: react-is@17.0.2: {} + react-is@18.3.1: {} + react@19.2.8: {} + readdirp@5.1.1: {} + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -3393,6 +4258,11 @@ snapshots: require-from-string@2.0.2: {} + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + rolldown@1.1.5: dependencies: "@oxc-project/types": 0.139.0 @@ -3436,16 +4306,45 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} + source-map@0.7.6: {} + stackback@0.0.2: {} std-env@4.2.0: {} + stdin-discarder@0.3.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@8.3.0: + dependencies: + get-east-asian-width: 1.7.0 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.5.0 + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + symbol-tree@3.2.4: {} tinybench@2.9.0: {} @@ -3457,6 +4356,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.5) picomatch: 4.0.5 + tinypool@2.2.0: {} + tinyrainbow@3.1.1: {} tldts-core@7.4.10: {} @@ -3465,6 +4366,10 @@ snapshots: dependencies: tldts-core: 7.4.10 + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + tough-cookie@6.0.2: dependencies: tldts: 7.4.10 @@ -3483,13 +4388,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.65.0(eslint@10.7.0)(typescript@6.0.3): + typescript-eslint@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3): dependencies: - "@typescript-eslint/eslint-plugin": 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0)(typescript@6.0.3))(eslint@10.7.0)(typescript@6.0.3) - "@typescript-eslint/parser": 8.65.0(eslint@10.7.0)(typescript@6.0.3) + "@typescript-eslint/eslint-plugin": 8.65.0(@typescript-eslint/parser@8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + "@typescript-eslint/parser": 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) "@typescript-eslint/typescript-estree": 8.65.0(typescript@6.0.3) - "@typescript-eslint/utils": 8.65.0(eslint@10.7.0)(typescript@6.0.3) - eslint: 10.7.0 + "@typescript-eslint/utils": 8.65.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.7.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -3510,7 +4415,11 @@ snapshots: dependencies: punycode: 2.3.1 - vite@8.1.5(@types/node@26.2.0)(yaml@2.8.2): + use-sync-external-store@1.7.0(react@19.2.8): + dependencies: + react: 19.2.8 + + vite@8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -3520,12 +4429,13 @@ snapshots: optionalDependencies: "@types/node": 26.2.0 fsevents: 2.3.3 + jiti: 2.7.0 yaml: 2.8.2 - vitest@4.1.10(@types/node@26.2.0)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.1.5(@types/node@26.2.0)(yaml@2.8.2)): + vitest@4.1.10(@types/node@26.2.0)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2)): dependencies: "@vitest/expect": 4.1.10 - "@vitest/mocker": 4.1.10(vite@8.1.5(@types/node@26.2.0)(yaml@2.8.2)) + "@vitest/mocker": 4.1.10(vite@8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2)) "@vitest/pretty-format": 4.1.10 "@vitest/runner": 4.1.10 "@vitest/snapshot": 4.1.10 @@ -3542,7 +4452,7 @@ snapshots: tinyexec: 1.3.0 tinyglobby: 0.2.17 tinyrainbow: 3.1.1 - vite: 8.1.5(@types/node@26.2.0)(yaml@2.8.2) + vite: 8.1.5(@types/node@26.2.0)(jiti@2.7.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: "@types/node": 26.2.0 @@ -3585,10 +4495,10 @@ snapshots: yaml@2.8.2: {} - yaml@2.9.1: {} - yocto-queue@0.1.0: {} + yoctocolors@2.2.0: {} + zod-validation-error@4.0.2(zod@4.4.3): dependencies: zod: 4.4.3 diff --git a/scripts/deploy-pages-dev.mjs b/scripts/deploy-pages-dev.mjs index 86699e2..a45dfaa 100644 --- a/scripts/deploy-pages-dev.mjs +++ b/scripts/deploy-pages-dev.mjs @@ -9,9 +9,7 @@ const deployment = plannerDeploymentFor(process.env); const environment = { ...process.env, TASKNOTES_PLANNER_URL: deployment.appOrigin, - ...(deployment.branch === "lab" ? { VITE_MDBASE_ENV: "lab" } : {}), - VITE_MDBASE_CONNECT_URL: deployment.connectOrigin, - VITE_MDBASE_CONNECT_LOOPBACK_URL: deployment.loopbackOrigin, + VITE_MDBASE_ENV: deployment.branch, }; await buildWithTemporaryManifest(environment, () => diff --git a/scripts/deploy-pages-production.mjs b/scripts/deploy-pages-production.mjs index fc7b6de..ebc2c88 100644 --- a/scripts/deploy-pages-production.mjs +++ b/scripts/deploy-pages-production.mjs @@ -6,13 +6,10 @@ import { verifyPlannerBuild } from "./verify-build.mjs"; const deployment = Object.freeze({ appOrigin: "https://planner.tasknotes.dev", connectOrigin: "https://connect.mdbase.dev", - loopbackOrigin: "http://127.0.0.1:28485", }); const environment = { ...process.env, TASKNOTES_PLANNER_URL: deployment.appOrigin, - VITE_MDBASE_CONNECT_URL: deployment.connectOrigin, - VITE_MDBASE_CONNECT_LOOPBACK_URL: deployment.loopbackOrigin, }; await run("pnpm", ["build"], environment); diff --git a/scripts/deployment-environment.mjs b/scripts/deployment-environment.mjs index 93e5f17..e708222 100644 --- a/scripts/deployment-environment.mjs +++ b/scripts/deployment-environment.mjs @@ -2,13 +2,11 @@ const deployments = Object.freeze({ staging: Object.freeze({ appOrigin: "https://staging.tasknotes-planner.pages.dev", connectOrigin: "https://connect-staging.mdbase.dev", - loopbackOrigin: "http://127.0.0.1:28486", branch: "staging", }), lab: Object.freeze({ appOrigin: "https://lab.tasknotes-planner.pages.dev", connectOrigin: "https://connect-lab.mdbase.dev", - loopbackOrigin: "http://127.0.0.1:28487", branch: "lab", }), }); diff --git a/scripts/deployment-environment.node-test.mjs b/scripts/deployment-environment.node-test.mjs index efd6157..a84b858 100644 --- a/scripts/deployment-environment.node-test.mjs +++ b/scripts/deployment-environment.node-test.mjs @@ -6,7 +6,6 @@ test("Planner LAB is an explicit fixed target; prior staging default is unchange assert.deepEqual(plannerDeploymentFor({ MDBASE_ENV: "lab" }), { appOrigin: "https://lab.tasknotes-planner.pages.dev", connectOrigin: "https://connect-lab.mdbase.dev", - loopbackOrigin: "http://127.0.0.1:28487", branch: "lab", }); assert.equal(plannerDeploymentFor({}).branch, "staging"); diff --git a/scripts/planner-manifest.mjs b/scripts/planner-manifest.mjs index 6213457..bb679cc 100644 --- a/scripts/planner-manifest.mjs +++ b/scripts/planner-manifest.mjs @@ -1,4 +1,4 @@ -import { parseAppManifest } from "@mdbase-dev/connect-protocol/manifest"; +import { parseAppManifest } from "@mdbase-dev/sdk/manifest"; import { loadCanonicalBasesTypePack, diff --git a/scripts/planner-sdk-session.test.mjs b/scripts/planner-sdk-session.test.mjs deleted file mode 100644 index 4cd115e..0000000 --- a/scripts/planner-sdk-session.test.mjs +++ /dev/null @@ -1,155 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { - MdbaseApplicationSession, - MdbaseMemorySelection, -} from "@mdbase-dev/connect"; -import { - capabilityOperationsForContractVersion, - APPLICATION_SETUP_OPERATIONS, -} from "@mdbase-dev/connect-protocol"; -import { buildPlannerManifest } from "./planner-manifest.mjs"; - -const ok = (value) => ({ ok: true, value, diagnostics: [] }); -const denied = (code) => ({ - ok: false, - problem: { code, message: code }, - diagnostics: [], -}); - -// Only the transport boundary is a fixture: lifecycle, setup checks, exact-contract -// verification and capability evaluation all execute the installed SDK. -async function fixture({ - stale = false, - contracts, - setupProblem, - describeProblem, -} = {}) { - const manifest = await buildPlannerManifest({ - appUrl: "https://planner.tasknotes.dev", - }); - const operations = [ - ...new Set([ - ...manifest.requirements.capabilities.required.flatMap((id) => - capabilityOperationsForContractVersion(2, id), - ), - ...APPLICATION_SETUP_OPERATIONS, - ]), - ]; - const info = { - collectionId: "fixture", - displayName: "Fixture", - operations, - scope: { access: "full_collection", contracts: [] }, - fileCapability: { - actions: [...manifest.requirements.files.required], - scope: manifest.requirements.files.scope, - }, - authority: { kind: "connector", durability: "computer" }, - route: "relay", - directAccess: "unavailable", - }; - const connection = { - collectionId: info.collectionId, - info: () => info, - onConnectionChange: () => () => {}, - authorizationCapabilities: (required) => ({ - authorized: true, - sufficient: required.every((op) => operations.includes(op)), - grantedOperations: operations, - missingOperations: required.filter((op) => !operations.includes(op)), - }), - assessCollectionSetup: vi.fn(async () => - setupProblem - ? denied(setupProblem) - : ok({ - status: "current", - applicable: true, - configuration: [], - typePacks: [], - }), - ), - applyCollectionSetup: vi.fn(), - describe: vi.fn(async () => - describeProblem - ? denied(describeProblem) - : ok({ - collectionId: info.collectionId, - contracts: contracts ?? manifest.requirements.contracts, - }), - ), - }; - const transport = { - register: async () => - ok({ - id: "v2-registration", - family_identity: `bundle:${manifest.id}`, - manifest_digest: "a".repeat(64), - }), - manifest: async () => ok(manifest), - connections: () => [info], - connection: () => connection, - connectionApplicationId: () => - stale ? "old-v1-registration" : "v2-registration", - onConnectionsChange: () => () => {}, - unavailableReason: () => null, - authorize: vi.fn(async () => denied("access_denied")), - }; - const selection = new MdbaseMemorySelection(); - selection.select(info.collectionId); - const session = new MdbaseApplicationSession(transport, { - selection, - autoSelect: "never", - }); - return { session, connection, transport }; -} - -describe("installed beta95 Planner session", () => { - it("requires explicit consent for a stale registration even with sufficient operations", async () => { - const { session, connection, transport } = await fixture({ stale: true }); - try { - expect((await session.start()).ok).toBe(true); - expect(session.getSnapshot().status).toBe("authorization_required"); - expect(transport.authorize).not.toHaveBeenCalled(); - expect(connection.assessCollectionSetup).not.toHaveBeenCalled(); - expect((await session.authorize("selected")).ok).toBe(false); - expect(transport.authorize).toHaveBeenCalledTimes(1); - expect(transport.authorize.mock.calls[0][0].target).toEqual({ - kind: "collection", - collectionId: "fixture", - }); - expect(session.getSnapshot().status).toBe("authorization_required"); - expect(connection.applyCollectionSetup).not.toHaveBeenCalled(); - } finally { - session.destroy(); - } - }); - - it.each([ - [{}, "ready"], - [{ contracts: [] }, "blocked"], - [ - { setupProblem: "application_declaration_mismatch" }, - "authorization_required", - ], - [{ describeProblem: "access_denied" }, "authorization_required"], - ])( - "checks exact contracts and setup before ready: %j", - async (options, status) => { - const { session, connection } = await fixture(options); - try { - expect((await session.start()).ok).toBe(true); - expect(session.getSnapshot().status).toBe(status); - expect(connection.assessCollectionSetup).toHaveBeenCalledTimes(1); - expect(connection.applyCollectionSetup).not.toHaveBeenCalled(); - if (!options.setupProblem) - expect(connection.describe).toHaveBeenCalledTimes(1); - if (status === "ready") - expect(session.getSnapshot().readiness.contracts.state).toBe( - "verified", - ); - } finally { - session.destroy(); - } - }, - ); -}); diff --git a/scripts/verify-build.mjs b/scripts/verify-build.mjs index ce8efcd..a2234d8 100644 --- a/scripts/verify-build.mjs +++ b/scripts/verify-build.mjs @@ -1,12 +1,8 @@ -import { readFile, readdir } from "node:fs/promises"; +import { readFile } from "node:fs/promises"; import { resolve } from "node:path"; import { fileURLToPath } from "node:url"; -export async function verifyPlannerBuild({ - appOrigin, - connectOrigin, - loopbackOrigin, -}) { +export async function verifyPlannerBuild({ appOrigin }) { const root = resolve(import.meta.dirname, "..", "dist"); const manifest = JSON.parse( await readFile(resolve(root, ".well-known", "mdbase-app.json"), "utf8"), @@ -35,23 +31,10 @@ export async function verifyPlannerBuild({ const notFound = await readFile(resolve(root, "404.html"), "utf8"); if (!notFound.includes("Page not found") || notFound.includes('id="root"')) throw new Error("Planner 404.html must be the not-found page."); - const scripts = (await readdir(resolve(root, "assets"))) - .filter((file) => file.endsWith(".js")) - .map((file) => resolve(root, "assets", file)); - const sources = await Promise.all( - scripts.map((script) => readFile(script, "utf8")), - ); - for (const expected of [connectOrigin, loopbackOrigin]) - if (!sources.some((source) => source.includes(expected))) - throw new Error(`Planner bundle does not contain ${expected}.`); } if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) await verifyPlannerBuild({ appOrigin: process.env.TASKNOTES_PLANNER_URL ?? "https://planner.tasknotes.dev", - connectOrigin: - process.env.VITE_MDBASE_CONNECT_URL ?? "https://connect.mdbase.dev", - loopbackOrigin: - process.env.VITE_MDBASE_CONNECT_LOOPBACK_URL ?? "http://127.0.0.1:28485", }); diff --git a/src/components/app.test.tsx b/src/components/app.test.tsx deleted file mode 100644 index 6e679fa..0000000 --- a/src/components/app.test.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { cleanup, fireEvent, render, screen } from "@testing-library/react"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -import { App } from "./app"; - -// Only the connection-screen boundary is a UI stand-in. The existing sample -// repository and Planner renderer are real; no legacy session/snapshot fixture. -vi.mock("./connection-gate", () => ({ - ConnectionGate: ({ onDemo }: { onDemo(): void }) => ( - - ), -})); - -const originalScrollTo = Object.getOwnPropertyDescriptor( - HTMLElement.prototype, - "scrollTo", -); -beforeEach(() => { - vi.stubGlobal( - "matchMedia", - vi.fn(() => ({ matches: false })), - ); - HTMLElement.prototype.scrollTo = vi.fn(); -}); - -afterEach(() => { - cleanup(); - vi.unstubAllGlobals(); - if (originalScrollTo) - Object.defineProperty(HTMLElement.prototype, "scrollTo", originalScrollTo); - else Reflect.deleteProperty(HTMLElement.prototype, "scrollTo"); - history.replaceState(null, "", "/"); -}); - -describe("first-run sample plan navigation", () => { - it("labels the real sample and preserves unrelated URL state when leaving a demo link", async () => { - history.replaceState( - { original: true }, - "", - "/?demo=1&keep=original#context", - ); - const view = render(); - fireEvent.click( - await screen.findByRole("button", { name: "Sample tasks" }), - ); - expect( - await screen.findByRole("button", { name: "Use sample tasks" }), - ).toBeTruthy(); - expect(new URL(location.href).searchParams.has("demo")).toBe(false); - expect(new URL(location.href).searchParams.get("keep")).toBe("original"); - expect(location.hash).toBe("#context"); - expect(history.state).toEqual({ original: true }); - view.unmount(); - render(); - expect( - await screen.findByRole("button", { name: "Use sample tasks" }), - ).toBeTruthy(); - expect(screen.queryByRole("button", { name: "Sample tasks" })).toBeNull(); - }); - it("can enter and leave samples from the ordinary first-run action", async () => { - history.replaceState(null, "", "/?keep=original"); - render(); - fireEvent.click( - await screen.findByRole("button", { name: "Use sample tasks" }), - ); - fireEvent.click( - await screen.findByRole("button", { name: "Sample tasks" }), - ); - expect( - await screen.findByRole("button", { name: "Use sample tasks" }), - ).toBeTruthy(); - expect(location.search).toBe("?keep=original"); - }); -}); diff --git a/src/components/app.tsx b/src/components/app.tsx deleted file mode 100644 index 9e8e622..0000000 --- a/src/components/app.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { lazy, Suspense } from "react"; - -import { DemoPlannerRepository } from "../data/demo-repository"; -import { Planner } from "./planner"; -import { useSampleMode } from "./use-sample-mode"; - -// Native mode must not instantiate the legacy Connect session or selection. -const ConnectionGate = lazy(async () => { - const module = await import("./connection-gate"); - return { default: module.ConnectionGate }; -}); - -/** Planner over legacy Connect (non-LAB builds). LAB builds use `SdkApp`. */ -export function App() { - const { demo, enter, leave } = useSampleMode(); - return demo ? ( - - ) : ( - -

Opening Planner…

- - } - > - -
- ); -} diff --git a/src/components/connection-gate.test.tsx b/src/components/connection-gate.test.tsx deleted file mode 100644 index 4979888..0000000 --- a/src/components/connection-gate.test.tsx +++ /dev/null @@ -1,382 +0,0 @@ -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; - -import type { MdbaseApplicationSessionSnapshot } from "@mdbase-dev/connect"; - -const connectMock = vi.hoisted(() => { - const ok = (value: Value) => ({ - ok: true as const, - value, - diagnostics: [], - }); - let snapshot: MdbaseApplicationSessionSnapshot = { - status: "unselected", - connections: [], - }; - const listeners = new Set<() => void>(); - const isAuthorizationCallback = vi.fn(() => false); - const session = { - applyCollectionSetup: vi.fn(), - authorize: vi.fn(), - clearSelection: vi.fn(), - connection: vi.fn(() => null), - getSnapshot: vi.fn(() => snapshot), - handleAuthorizationCallback: vi.fn(), - select: vi.fn(), - start: vi.fn(), - subscribe: vi.fn((listener: () => void) => { - listeners.add(listener); - return () => listeners.delete(listener); - }), - }; - return { - session, - isAuthorizationCallback, - reset() { - snapshot = { status: "unselected", connections: [] }; - listeners.clear(); - for (const mock of Object.values(session)) { - if ("mockReset" in mock) mock.mockReset(); - } - session.connection.mockReturnValue(null); - session.start.mockResolvedValue(ok(snapshot)); - session.authorize.mockResolvedValue(ok({ kind: "redirected" })); - session.applyCollectionSetup.mockResolvedValue(ok(snapshot)); - session.handleAuthorizationCallback.mockResolvedValue(ok({})); - session.select.mockReturnValue(ok({})); - session.clearSelection.mockReturnValue(ok(undefined)); - isAuthorizationCallback.mockReset(); - isAuthorizationCallback.mockReturnValue(false); - }, - setSnapshot(next: MdbaseApplicationSessionSnapshot) { - snapshot = next; - for (const listener of listeners) listener(); - }, - }; -}); - -vi.mock("../data/connect", () => ({ - assertPlannerOrigin: vi.fn(), - isAuthorizationCallback: connectMock.isAuthorizationCallback, - plannerSession: connectMock.session, -})); - -import { SetupReview, WelcomePreview } from "./connection-gate"; - -beforeEach(() => connectMock.reset()); - -describe("ConnectionGate lifecycle", () => { - it("retries a failed explicit start and gates collection actions", async () => { - connectMock.session.start - .mockImplementationOnce(async () => { - const problem = { - code: "temporarily_unavailable", - message: "Connect is temporarily unavailable.", - }; - connectMock.setSnapshot({ - status: "start_failed", - problem, - connections: [], - } as unknown as MdbaseApplicationSessionSnapshot); - return { ok: false as const, problem, diagnostics: [] }; - }) - .mockImplementationOnce(async () => { - const snapshot: MdbaseApplicationSessionSnapshot = { - status: "unselected", - connections: [], - }; - connectMock.setSnapshot(snapshot); - return { ok: true as const, value: snapshot, diagnostics: [] }; - }); - - const { ConnectionGate } = await import("./connection-gate"); - render(); - - expect( - await screen.findByRole("button", { name: "Retry opening Planner" }), - ).toBeInTheDocument(); - expect( - screen.queryByRole("button", { name: "Open collection" }), - ).not.toBeInTheDocument(); - - fireEvent.click( - screen.getByRole("button", { name: "Retry opening Planner" }), - ); - - await screen.findByRole("button", { name: "Open collection" }); - expect(connectMock.session.start).toHaveBeenCalledTimes(2); - }); - - it("makes an abandoned startup timeout retryable from not_started", async () => { - connectMock.session.start - .mockImplementationOnce(async () => { - connectMock.setSnapshot({ status: "starting", connections: [] }); - await Promise.resolve(); - connectMock.setSnapshot({ status: "not_started", connections: [] }); - return { - ok: false as const, - problem: { code: "timeout", message: "Startup timed out." }, - }; - }) - .mockImplementationOnce(async () => { - const snapshot: MdbaseApplicationSessionSnapshot = { - status: "unselected", - connections: [], - }; - connectMock.setSnapshot(snapshot); - return { ok: true as const, value: snapshot, diagnostics: [] }; - }); - - const { ConnectionGate } = await import("./connection-gate"); - render(); - - fireEvent.click( - await screen.findByRole("button", { name: "Retry opening Planner" }), - ); - await screen.findByRole("button", { name: "Open collection" }); - - expect(connectMock.session.start).toHaveBeenNthCalledWith(1, { - timeoutMs: 20_000, - }); - expect(connectMock.session.start).toHaveBeenCalledTimes(2); - }); - - it("lets startup own callback completion with the callback budget", async () => { - connectMock.isAuthorizationCallback.mockReturnValue(true); - const { ConnectionGate } = await import("./connection-gate"); - render(); - - await waitFor(() => - expect(connectMock.session.start).toHaveBeenCalledWith({ - timeoutMs: 60_000, - }), - ); - expect( - connectMock.session.handleAuthorizationCallback, - ).not.toHaveBeenCalled(); - }); - - it("reviews declaration changes through explicit selected authorization", async () => { - connectMock.setSnapshot(authorizationRequiredSnapshot()); - const { ConnectionGate } = await import("./connection-gate"); - render(); - - await screen.findByText( - /previous grant will not be upgraded automatically/, - ); - expect(connectMock.session.authorize).not.toHaveBeenCalled(); - fireEvent.click( - await screen.findByRole("button", { name: "Review updated access" }), - ); - - await waitFor(() => - expect(connectMock.session.authorize).toHaveBeenCalledWith("selected", { - timeoutMs: 60_000, - }), - ); - }); - - it("shows denied consent without falling back or applying setup", async () => { - connectMock.setSnapshot(authorizationRequiredSnapshot()); - connectMock.session.authorize.mockResolvedValue({ - ok: false, - problem: { code: "access_denied", message: "Authorization denied." }, - diagnostics: [], - }); - const { ConnectionGate } = await import("./connection-gate"); - render(); - fireEvent.click( - await screen.findByRole("button", { name: "Review updated access" }), - ); - expect(await screen.findByRole("alert")).toHaveTextContent( - "Authorization denied.", - ); - expect(connectMock.session.authorize).toHaveBeenCalledTimes(1); - expect(connectMock.session.applyCollectionSetup).not.toHaveBeenCalled(); - }); - - it("omits the selected unusable collection from alternatives", async () => { - const selected = authorizationRequiredSnapshot(); - connectMock.setSnapshot({ - ...selected, - connections: [ - selected.info, - { ...selected.info, collectionId: "collection-2", displayName: "Home" }, - ], - }); - const { ConnectionGate } = await import("./connection-gate"); - render(); - - expect( - await screen.findByRole("button", { name: "Open Home" }), - ).toBeInTheDocument(); - expect( - screen.queryByRole("button", { name: "Open Product" }), - ).not.toBeInTheDocument(); - - fireEvent.click(screen.getByRole("button", { name: "Open Home" })); - expect(connectMock.session.select).toHaveBeenCalledWith("collection-2", { - history: "replace", - }); - }); - - it("reauthorizes an unavailable selected collection in place", async () => { - connectMock.setSnapshot({ - status: "unavailable", - collectionId: "collection-1", - reason: "invalid_stored_grant", - connections: [], - }); - const { ConnectionGate } = await import("./connection-gate"); - render(); - - fireEvent.click( - await screen.findByRole("button", { name: "Review updated access" }), - ); - - await waitFor(() => - expect(connectMock.session.authorize).toHaveBeenCalledWith("selected", { - timeoutMs: 60_000, - }), - ); - }); - - it("keeps destroyed sessions terminal", async () => { - connectMock.setSnapshot({ status: "destroyed", connections: [] }); - const { ConnectionGate } = await import("./connection-gate"); - render(); - - expect( - await screen.findByText( - "This collection session has closed. Reload Planner to reconnect.", - ), - ).toBeInTheDocument(); - expect( - screen.queryByRole("button", { name: /open collection|retry opening/i }), - ).not.toBeInTheDocument(); - expect(connectMock.session.start).not.toHaveBeenCalled(); - }); -}); - -function authorizationRequiredSnapshot(): Extract< - MdbaseApplicationSessionSnapshot, - { status: "authorization_required" } -> { - return { - status: "authorization_required", - collectionId: "collection-1", - info: { - collectionId: "collection-1", - displayName: "Product", - operations: [], - scope: { kind: "collection" }, - authority: { kind: "connector", durability: "computer" }, - route: "relay", - directAccess: "unavailable", - }, - capabilities: { - requiredAvailable: false, - optionalAvailable: false, - available: [], - unavailable: [], - }, - connections: [], - } as unknown as Extract< - MdbaseApplicationSessionSnapshot, - { status: "authorization_required" } - >; -} - -describe("WelcomePreview", () => { - it("moves example tasks with pointer dragging", () => { - render(); - const task = screen.getByRole("button", { name: "Research: move task" }); - Object.defineProperty(task.parentElement, "getBoundingClientRect", { - value: () => ({ width: 500 }), - }); - - fireEvent.pointerDown(task, { clientX: 100, pointerId: 1 }); - fireEvent.pointerMove(task, { clientX: 200, pointerId: 1 }); - fireEvent.pointerUp(task, { clientX: 200, pointerId: 1 }); - - expect(task).toHaveStyle("--preview-start: 25"); - }); - - it("moves example tasks with the keyboard", () => { - render(); - const task = screen.getByRole("button", { name: "Release: move task" }); - - fireEvent.keyDown(task, { key: "ArrowRight" }); - - expect(task).toHaveStyle("--preview-start: 45"); - }); -}); - -describe("SetupReview", () => { - it("shows the reviewed collection changes without implying record changes", () => { - const snapshot = { - status: "setup_review_required", - collectionId: "collection-1", - info: { - collectionId: "collection-1", - displayName: "Product", - operations: [], - scope: { kind: "collection" }, - authority: { kind: "connector", durability: "computer" }, - route: "relay", - directAccess: "unavailable", - }, - capabilities: { - requiredAvailable: true, - optionalAvailable: true, - available: [], - unavailable: [], - }, - connections: [], - update: { - status: "provision", - applicable: true, - assessmentDigest: "sha256:assessment", - collectionRevision: "revision-1", - provisionDigest: "sha256:provision", - canApply: true, - reason: "", - configuration: [ - { - requirement: "tasknotes-bases", - path: "x-obsidian.bases.include", - value: "TaskNotes/Views/**/*.base", - action: "add", - }, - ], - typePacks: [ - { - id: "tasknotes.task", - name: "TaskNotes task", - status: "install", - applicable: true, - assessmentDigest: "sha256:pack", - desiredVersion: "0.3.0-rc.10", - resources: [], - contractSetups: [], - canApply: true, - reason: "", - }, - ], - }, - } as unknown as Extract< - MdbaseApplicationSessionSnapshot, - { status: "setup_review_required" } - >; - - render(); - - expect(screen.getByText("Product")).toBeInTheDocument(); - expect(screen.getByText("TaskNotes task")).toBeInTheDocument(); - expect( - screen.getByText( - "Task records, custom types, and unrelated collection settings will stay as they are.", - ), - ).toBeInTheDocument(); - expect(screen.getByText("TaskNotes/Views/**/*.base")).toBeInTheDocument(); - }); -}); diff --git a/src/components/connection-gate.tsx b/src/components/connection-gate.tsx deleted file mode 100644 index 66697d6..0000000 --- a/src/components/connection-gate.tsx +++ /dev/null @@ -1,526 +0,0 @@ -import { Check, Database, LoaderCircle } from "lucide-react"; -import { - useCallback, - useEffect, - useMemo, - useRef, - useState, - useSyncExternalStore, -} from "react"; - -import type { - CSSProperties, - KeyboardEvent as ReactKeyboardEvent, - PointerEvent as ReactPointerEvent, -} from "react"; -import type { MdbaseApplicationSessionSnapshot } from "@mdbase-dev/connect"; - -import { - assertPlannerOrigin, - isAuthorizationCallback, - plannerSession, -} from "../data/connect"; -import { MdbasePlannerRepository } from "../data/mdbase-repository"; -import { errorMessage, requireOutcome } from "../data/outcome"; -import { Planner } from "./planner"; -import { NativeSignIn } from "./native-signin"; -import type { createNativeSignIn } from "../data/native-signin-ui"; - -const START_TIMEOUT_MS = 20_000; -const CALLBACK_START_TIMEOUT_MS = 60_000; - -export function ConnectionGate({ - onDemo, - nativeSignIn, -}: { - onDemo(): void; - nativeSignIn?: { - binding: ReturnType; - onSignedOut(): void; - }; -}) { - // Real native composition supplies authenticated build inputs and owns the SDK - // lifetime. Until delivered, preserve the existing default runtime unchanged. - return nativeSignIn ? ( - - ) : ( - - ); -} - -function LegacyConnectionGate({ onDemo }: { onDemo(): void }) { - const snapshot = useSyncExternalStore( - (listener) => plannerSession.subscribe(listener), - () => plannerSession.getSnapshot(), - () => plannerSession.getSnapshot(), - ); - const [error, setError] = useState(""); - const [startError, setStartError] = useState(""); - const [opening, setOpening] = useState(false); - const mounted = useRef(true); - - const startSession = useCallback(async () => { - setStartError(""); - try { - assertPlannerOrigin(); - const timeoutMs = isAuthorizationCallback(location.href) - ? CALLBACK_START_TIMEOUT_MS - : START_TIMEOUT_MS; - requireOutcome(await plannerSession.start({ timeoutMs })); - } catch (reason) { - if (mounted.current) setStartError(connectionErrorMessage(reason)); - } - }, []); - - useEffect(() => { - let active = true; - mounted.current = true; - const status = plannerSession.getSnapshot().status; - if (status !== "start_failed" && status !== "destroyed") { - queueMicrotask(() => { - if (active) void startSession(); - }); - } - return () => { - active = false; - mounted.current = false; - }; - }, [startSession]); - - const connection = - snapshot.status === "ready" ? plannerSession.connection() : null; - const repository = useMemo( - () => (connection ? new MdbasePlannerRepository(connection) : null), - [connection], - ); - - const authorize = useCallback(async () => { - setOpening(true); - setError(""); - try { - requireOutcome( - await plannerSession.authorize("choose", { timeoutMs: 60_000 }), - ); - } catch (reason) { - setError(connectionErrorMessage(reason)); - } finally { - setOpening(false); - } - }, []); - - const applySetup = useCallback(async () => { - setOpening(true); - setError(""); - try { - requireOutcome( - await plannerSession.applyCollectionSetup({ timeoutMs: 60_000 }), - ); - } catch (reason) { - setError(connectionErrorMessage(reason)); - } finally { - setOpening(false); - } - }, []); - - const reauthorize = useCallback(async () => { - setOpening(true); - setError(""); - try { - requireOutcome( - await plannerSession.authorize("selected", { timeoutMs: 60_000 }), - ); - } catch (reason) { - setError(connectionErrorMessage(reason)); - } finally { - setOpening(false); - } - }, []); - - const selectCollection = useCallback((collectionId: string) => { - setError(""); - try { - requireOutcome( - plannerSession.select(collectionId, { history: "replace" }), - ); - } catch (reason) { - setError(connectionErrorMessage(reason)); - } - }, []); - - const clearSelection = useCallback(() => { - requireOutcome(plannerSession.clearSelection()); - }, []); - - if (repository) - return ( - - ); - - const stateError = - startError || - (snapshot.status === "start_failed" - ? snapshot.problem.message - : snapshot.status === "destroyed" - ? "This collection session has closed. Reload Planner to reconnect." - : snapshot.status === "blocked" - ? snapshot.problem.message - : snapshot.status === "unavailable" - ? unavailableMessage(snapshot.reason) - : ""); - - const retryStartup = - snapshot.status === "start_failed" || - (snapshot.status === "not_started" && Boolean(startError)); - - const isWaiting = - snapshot.status === "not_started" || - snapshot.status === "starting" || - snapshot.status === "checking_setup"; - const selectedCollectionId = - "collectionId" in snapshot ? snapshot.collectionId : null; - const sessionStarted = ![ - "not_started", - "starting", - "start_failed", - "destroyed", - ].includes(snapshot.status); - const alternativeConnections = sessionStarted - ? snapshot.connections.filter( - ({ collectionId }) => collectionId !== selectedCollectionId, - ) - : []; - - return ( -
-
-
- - TaskNotes Planner -
- {snapshot.status === "setup_review_required" ? ( - <> -

Finish opening this collection.

-

- Review the TaskNotes files and settings Planner needs, then apply - them to continue. -

- - ) : ( - <> -

Plan tasks on a timeline.

-

- Open a TaskNotes collection to change dates and manage - dependencies. -

- - )} -
- {retryStartup ? ( - - ) : snapshot.status === "destroyed" ? null : snapshot.status === - "setup_review_required" ? ( - - ) : snapshot.status === "authorization_required" || - snapshot.status === "unavailable" ? ( - - ) : ( - - )} - - {snapshot.status === "setup_review_required" ? ( - - ) : null} - {alternativeConnections.map((connection) => ( - - ))} -
- {snapshot.status === "authorization_required" ? ( -

- Planner needs updated access. Review and approve the new permission - groups to continue; your previous grant will not be upgraded - automatically. -

- ) : null} - {error || stateError ? ( -

- {error || stateError} -

- ) : null} -
- {snapshot.status === "setup_review_required" ? ( - - ) : ( - - )} -
- ); -} - -type SetupReviewSnapshot = Extract< - MdbaseApplicationSessionSnapshot, - { status: "setup_review_required" } ->; - -export function SetupReview({ snapshot }: { snapshot: SetupReviewSnapshot }) { - const changes = snapshot.update.configuration.filter( - (item) => item.action !== "current", - ); - - return ( -
-
- COLLECTION SETUP - {snapshot.info.displayName} -
-
-

Changes to apply

-

- Task records, custom types, and unrelated collection settings will - stay as they are. -

-
-
    - {snapshot.update.typePacks.map((pack) => ( -
  • - -
    - {pack.name} - - {pack.currentVersion - ? `${pack.currentVersion} → ${pack.desiredVersion}` - : `Install ${pack.desiredVersion}`} - -
    -
  • - ))} - {changes.map((item) => ( -
  • - -
    - - {item.action === "conflict" - ? "Collection setting needs attention" - : "Allow TaskNotes Base views"} - - - {item.action === "conflict" - ? item.conflict?.message - : String(item.value)} - -
    -
  • - ))} -
- {!snapshot.update.canApply ? ( -

- {snapshot.update.reason} -

- ) : null} -
- ); -} - -const previewTasks = [ - { label: "Research", start: 5, width: 26 }, - { label: "Prototype", start: 18, width: 24 }, - { label: "Review", start: 30, width: 22 }, - { label: "Release", start: 43, width: 20 }, -] as const; - -type PreviewDrag = { - index: number; - pointerId: number; - startClientX: number; - startOffset: number; - trackWidth: number; -}; - -export function WelcomePreview() { - const [starts, setStarts] = useState(() => - previewTasks.map((task) => task.start), - ); - const drag = useRef(null); - - const moveTask = useCallback((index: number, nextStart: number) => { - setStarts((current) => { - const maximum = 100 - previewTasks[index].width; - const clamped = Math.min(maximum, Math.max(0, nextStart)); - if (clamped === current[index]) return current; - return current.map((start, taskIndex) => - taskIndex === index ? clamped : start, - ); - }); - }, []); - - const startDrag = useCallback( - (index: number, event: ReactPointerEvent) => { - const track = event.currentTarget.parentElement; - if (!track) return; - event.preventDefault(); - event.currentTarget.setPointerCapture?.(event.pointerId); - drag.current = { - index, - pointerId: event.pointerId, - startClientX: event.clientX, - startOffset: starts[index], - trackWidth: track.getBoundingClientRect().width, - }; - }, - [starts], - ); - - const continueDrag = useCallback( - (event: ReactPointerEvent) => { - const active = drag.current; - if (!active || active.pointerId !== event.pointerId) return; - const delta = - ((event.clientX - active.startClientX) / active.trackWidth) * 100; - moveTask(active.index, active.startOffset + delta); - }, - [moveTask], - ); - - const finishDrag = useCallback( - (event: ReactPointerEvent) => { - if (drag.current?.pointerId !== event.pointerId) return; - event.currentTarget.releasePointerCapture?.(event.pointerId); - drag.current = null; - }, - [], - ); - - const moveWithKeyboard = useCallback( - (index: number, event: ReactKeyboardEvent) => { - if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return; - event.preventDefault(); - const direction = event.key === "ArrowLeft" ? -1 : 1; - moveTask(index, starts[index] + direction * (event.shiftKey ? 5 : 2)); - }, - [moveTask, starts], - ); - - return ( -
-
- LAUNCH PLAN - AUG — SEP -
- {previewTasks.map((task, index) => ( -
- {task.label} -
-
-
- ))} - TODAY -
- ); -} - -function connectionErrorMessage(reason: unknown): string { - const message = errorMessage(reason); - if (/unknown application or redirect uri/i.test(message)) - return "This Planner address is not registered. Open Planner from its main URL and try again."; - return message; -} - -function unavailableMessage(reason: string): string { - if (reason === "authorization_lost") - return "The collection authorization is no longer available."; - if (reason === "invalid_stored_grant") - return "The saved collection connection is no longer valid."; - return "This collection needs to be authorized again."; -} diff --git a/src/components/gantt-chart.test.tsx b/src/components/gantt-chart.test.tsx index b1e3cf9..0b81874 100644 --- a/src/components/gantt-chart.test.tsx +++ b/src/components/gantt-chart.test.tsx @@ -160,31 +160,6 @@ describe("GanttChart interactions", () => { expect(onScheduleChange).not.toHaveBeenCalled(); }); - it("gates timeline writes while keeping task selection available", () => { - const task = makeTask({ - id: "blocked", - title: "Blocked task", - scheduled: "2026-08-17", - due: "2026-08-18", - }); - const { onSelect, onScheduleChange, onCompletionChange } = renderChart( - [task], - { mutationsDisabled: true }, - ); - - expect( - screen.getByRole("button", { name: "Complete Blocked task" }), - ).toBeDisabled(); - expect(bar("blocked")).not.toHaveClass("is-draggable"); - expect(bar("blocked").querySelector(".tnc-port, .tnc-resize")).toBeNull(); - - fireEvent.keyDown(bar("blocked"), { altKey: true, key: "ArrowRight" }); - fireEvent.click(bar("blocked")); - expect(onSelect).toHaveBeenCalledWith(task); - expect(onScheduleChange).not.toHaveBeenCalled(); - expect(onCompletionChange).not.toHaveBeenCalled(); - }); - it("zooms with Ctrl+wheel inside the chart and leaves plain wheel scrolling alone", () => { const task = makeTask({ id: "zoomable", scheduled: "2026-08-17" }); const { onZoom } = renderChart([task]); diff --git a/src/components/gantt-chart.tsx b/src/components/gantt-chart.tsx index 5277c3a..920e10a 100644 --- a/src/components/gantt-chart.tsx +++ b/src/components/gantt-chart.tsx @@ -71,7 +71,6 @@ interface GanttChartProps { zoom: TimelineZoom; selectedId: string | null; todayRequest: number; - mutationsDisabled?: boolean; onSelect(task: PlannerTask): void; onZoom(direction: -1 | 1): void; onScheduleChange(task: PlannerTask, update: ScheduleUpdate): Promise; @@ -99,7 +98,6 @@ function GanttTimeline({ zoom, selectedId, todayRequest, - mutationsDisabled = false, onSelect, onZoom, onScheduleChange, @@ -182,7 +180,6 @@ function GanttTimeline({ task: PlannerTask, update: ScheduleUpdate, ): Promise { - if (mutationsDisabled) return false; showMessage({ tone: "neutral", text: "Saving schedule…" }, 10_000); try { await onScheduleChange(task, update); @@ -206,7 +203,6 @@ function GanttTimeline({ task: PlannerTask, dependencies: readonly TaskDependency[], ) { - if (mutationsDisabled) return; showMessage({ tone: "neutral", text: "Saving relationship…" }, 10_000); try { await onDependenciesChange(task, dependencies); @@ -225,7 +221,6 @@ function GanttTimeline({ } async function commitCompletion(task: PlannerTask) { - if (mutationsDisabled) return; if (task.recurrence) { showMessage( { @@ -329,7 +324,7 @@ function GanttTimeline({ task: PlannerTask, ) { const calendar = calendarRef.current; - if (mutationsDisabled || event.button !== 0 || !calendar) return; + if (event.button !== 0 || !calendar) return; const origin = event.nativeEvent; const doc = event.currentTarget.ownerDocument; const touch = origin.pointerType === "touch"; @@ -384,8 +379,6 @@ function GanttTimeline({ } function itemLabel(task: PlannerTask): string { - if (mutationsDisabled) - return `${task.title}, ${scheduleLabel(task)}. Select to review.`; const step = scale.intraday && taskHasTime(task) ? scale.snapMinutes === 15 @@ -401,7 +394,7 @@ function GanttTimeline({ toolbar: false, timeZone: "local", height: "fill", - editable: !mutationsDisabled, + editable: true, selectable: false, navLinks: false, selected: selectedId, @@ -430,14 +423,10 @@ function GanttTimeline({ next.delete(row.id); return next; }), - resizeHandleLabel: mutationsDisabled - ? undefined - : ({ item, edge }) => - `Change ${item.title} ${edge === "start" ? "scheduled" : "due"} date`, - onLink: mutationsDisabled - ? undefined - : ({ from, to, fromEdge, toEdge }) => - linkTasks(from.id, to.id, fromEdge, toEdge), + resizeHandleLabel: ({ item, edge }) => + `Change ${item.title} ${edge === "start" ? "scheduled" : "due"} date`, + onLink: ({ from, to, fromEdge, toEdge }) => + linkTasks(from.id, to.id, fromEdge, toEdge), canLink: ({ from, to }) => { const allowed = canAddDependency(allTasks, from.id, to.id); return allowed.allowed || allowed.reason; @@ -559,7 +548,6 @@ function GanttTimeline({ ) : ( ; onBeginPlacement( @@ -613,7 +599,7 @@ function TaskLedger({ ): void; }) { const inverted = hasInvertedSchedule(task); - const placeable = !taskSpan(task) && !mutationsDisabled; + const placeable = !taskSpan(task); return (
- ) : !view.paired || view.error ? ( - - ) : null} - {view.paired ? ( - - ) : null} - {snapshot.selectedCollectionId ? ( - - ) : null} -
- - - ); -} diff --git a/src/components/planner.empty.test.tsx b/src/components/planner.empty.test.tsx index 486febf..fc74a37 100644 --- a/src/components/planner.empty.test.tsx +++ b/src/components/planner.empty.test.tsx @@ -13,7 +13,6 @@ beforeEach(() => { ); HTMLElement.prototype.scrollTo = vi.fn(); vi.stubEnv("VITE_MDBASE_ENV", ""); - vi.stubEnv("VITE_MDBASE_CONNECT_URL", ""); }); afterEach(() => { cleanup(); @@ -38,22 +37,13 @@ async function showCollection(patch: Partial = {}) { describe("Planner empty states", () => { it.each([ - [ - "lab", - "https://connect-lab.mdbase.dev", - "https://lab.tasknotes-app.pages.dev", - ], - [ - "", - "https://connect-staging.mdbase.dev", - "https://staging.tasknotes-app.pages.dev", - ], - ["", "", "https://app.tasknotes.dev"], + ["lab", "https://lab.tasknotes-app.pages.dev"], + ["staging", "https://staging.tasknotes-app.pages.dev"], + ["", "https://app.tasknotes.dev"], ])( - "hands an empty collection to TaskNotes in its deployment (%s, %s)", - async (environment, connectUrl, origin) => { + "hands an empty collection to TaskNotes in its deployment (%s)", + async (environment, origin) => { vi.stubEnv("VITE_MDBASE_ENV", environment); - vi.stubEnv("VITE_MDBASE_CONNECT_URL", connectUrl); history.replaceState( null, "", diff --git a/src/components/planner.test.tsx b/src/components/planner.test.tsx index 7a26e28..5ae1965 100644 --- a/src/components/planner.test.tsx +++ b/src/components/planner.test.tsx @@ -5,12 +5,11 @@ import { waitFor, within, } from "@testing-library/react"; -import { MdbaseConnectError, type ConnectProblem } from "@mdbase-dev/connect"; +import { MdbaseError } from "@mdbase-dev/sdk"; import { Planner } from "./planner"; import type { - PendingPlannerMutation, PlannerCollection, PlannerRepository, PlannerTask, @@ -20,14 +19,12 @@ import type { vi.mock("./gantt-chart", () => ({ GanttChart: ({ tasks, - mutationsDisabled, onCompletionChange, onDependenciesChange, onScheduleChange, onSelect, }: { tasks: readonly PlannerTask[]; - mutationsDisabled?: boolean; onCompletionChange(task: PlannerTask): Promise; onDependenciesChange( task: PlannerTask, @@ -44,7 +41,6 @@ vi.mock("./gantt-chart", () => ({ Select test task - - ))} {rejectedWrites.map((write) => ( ))} - {recoveryError ? ( -

- {recoveryError} -

- ) : null} {error ? (

Tasks may be out of date. {error} @@ -603,7 +542,6 @@ export function Planner({ diff --git a/src/components/task-inspector.test.tsx b/src/components/task-inspector.test.tsx index e691c0e..5fd3e15 100644 --- a/src/components/task-inspector.test.tsx +++ b/src/components/task-inspector.test.tsx @@ -7,7 +7,7 @@ import { } from "@testing-library/react"; import { TaskInspector } from "./task-inspector"; -import { MdbaseConnectError, type ConnectProblem } from "@mdbase-dev/connect"; +import { MdbaseError } from "@mdbase-dev/sdk"; import type { PlannerTask } from "../domain/task"; @@ -164,15 +164,13 @@ describe("TaskInspector", () => { expect(screen.getByLabelText("Status")).toHaveValue("done"); }); - it("does not roll an uncertain property choice back before recovery", async () => { - const problem = { - problem_version: 1, - code: "operation_outcome_unknown", - category: "transport", - recovery: "recover_mutation", + it("does not roll an uncertain property choice back", async () => { + const problem = new MdbaseError({ + code: "outcome_unknown", + recovery: "refresh", message: "The status change may already have completed.", - operation_outcome: "unknown", - } as unknown as ConnectProblem; + }); + const onSaveProperties = vi.fn().mockRejectedValue(problem); render( { onClose={() => undefined} onSave={vi.fn().mockResolvedValue(undefined)} onSaveDependencies={vi.fn().mockResolvedValue(undefined)} - onSaveProperties={vi - .fn() - .mockRejectedValue(new MdbaseConnectError(problem))} + onSaveProperties={onSaveProperties} />, ); @@ -190,47 +186,11 @@ describe("TaskInspector", () => { target: { value: "done" }, }); - await screen.findByText("The status change may already have completed."); + await waitFor(() => expect(onSaveProperties).toHaveBeenCalled()); + await screen.findByRole("alert"); expect(screen.getByLabelText("Status")).toHaveValue("done"); }); - it("disables mutation-producing inspector controls while recovery is pending", () => { - const onSave = vi.fn(); - const onSaveDependencies = vi.fn(); - const onSaveProperties = vi.fn(); - render( - undefined} - onSave={onSave} - onSaveDependencies={onSaveDependencies} - onSaveProperties={onSaveProperties} - />, - ); - - expect(screen.getByLabelText("Status")).toBeDisabled(); - expect(screen.getByLabelText("Priority")).toBeDisabled(); - expect(screen.getByLabelText("Scheduled")).toBeDisabled(); - expect( - screen.getByRole("button", { name: "Save schedule" }), - ).toBeDisabled(); - expect( - screen.getByRole("button", { - name: "Remove relationship from research", - }), - ).toBeDisabled(); - - fireEvent.change(screen.getByLabelText("Status"), { - target: { value: "done" }, - }); - fireEvent.click(screen.getByRole("button", { name: "Save schedule" })); - expect(onSave).not.toHaveBeenCalled(); - expect(onSaveDependencies).not.toHaveBeenCalled(); - expect(onSaveProperties).not.toHaveBeenCalled(); - }); - it("adds and clears TaskNotes project memberships", async () => { const launch = { ...task, diff --git a/src/components/task-inspector.tsx b/src/components/task-inspector.tsx index c73d117..33692b4 100644 --- a/src/components/task-inspector.tsx +++ b/src/components/task-inspector.tsx @@ -34,7 +34,6 @@ import type { FormEvent } from "react"; export function TaskInspector({ task, allTasks, - mutationsDisabled = false, onClose, onSave, onSaveDependencies, @@ -42,7 +41,6 @@ export function TaskInspector({ }: { task: PlannerTask; allTasks: readonly PlannerTask[]; - mutationsDisabled?: boolean; onClose(): void; onSave(task: PlannerTask, update: ScheduleUpdate): Promise; onSaveDependencies( @@ -125,7 +123,7 @@ export function TaskInspector({ async function submit(event: FormEvent) { event.preventDefault(); - if (validation || mutationsDisabled) return; + if (validation) return; setSaving(true); setScheduleError(""); try { @@ -141,7 +139,6 @@ export function TaskInspector({ } async function saveDependencies(dependencies: readonly TaskDependency[]) { - if (mutationsDisabled) return; setDependencySaving(true); setDependencyError(""); try { @@ -154,7 +151,7 @@ export function TaskInspector({ } async function addDependency() { - if (!dependencySource || mutationsDisabled) return; + if (!dependencySource) return; const allowed = canAddDependency(allTasks, dependencySource, task.id); if (!allowed.allowed) { setDependencyError(allowed.reason); @@ -172,7 +169,6 @@ export function TaskInspector({ } async function saveProperty(update: TaskPropertyUpdate) { - if (mutationsDisabled) return; setPropertySaving(true); setPropertyError(""); try { @@ -220,7 +216,7 @@ export function TaskInspector({ -

+
Projects

Planner groups tasks by their first selected project.

{projectOptions.length ? ( @@ -337,14 +330,13 @@ export function TaskInspector({
setScheduled(event.target.value)} /> setDue(event.target.value)} />