diff --git a/docs/api-reference/fabro-api.yaml b/docs/api-reference/fabro-api.yaml index b6d4c77d0..72ce406ed 100644 --- a/docs/api-reference/fabro-api.yaml +++ b/docs/api-reference/fabro-api.yaml @@ -798,6 +798,142 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" + /api/v1/runs/{id}/rewind: + post: + operationId: rewindRun + tags: [Runs] + summary: Rewind Run + description: > + Creates a new run from an earlier checkpoint of a terminal source run, + archives the source run, and records `run.superseded_by` on the source + after archive succeeds. Returns 207 when the new run was created but + the source archive step failed. + parameters: + - $ref: "#/components/parameters/RunId" + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/RewindRequest" + responses: + "200": + description: Source archived and new run created + content: + application/json: + schema: + $ref: "#/components/schemas/RewindResponse" + "207": + description: New run created but source archive failed + content: + application/json: + schema: + $ref: "#/components/schemas/RewindResponse" + "400": + description: Invalid rewind target + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "409": + description: Source run is archived or is not terminal + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "501": + description: Server cannot access the run working directory + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/fork: + post: + operationId: forkRun + tags: [Runs] + summary: Fork Run + description: > + Creates a new run from a checkpoint of the source run. The source run + is left untouched. + parameters: + - $ref: "#/components/parameters/RunId" + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/ForkRequest" + responses: + "200": + description: New run created + content: + application/json: + schema: + $ref: "#/components/schemas/ForkResponse" + "400": + description: Invalid fork target + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "409": + description: Source run is archived + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "501": + description: Server cannot access the run working directory + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/timeline: + get: + operationId: getRunTimeline + tags: [Runs] + summary: Get Run Timeline + description: > + Returns checkpoint timeline entries read from the run metadata branch. + This endpoint does not rebuild missing metadata branches. + parameters: + - $ref: "#/components/parameters/RunId" + responses: + "200": + description: Run checkpoint timeline + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/TimelineEntryResponse" + "404": + description: Run not found + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "501": + description: Server cannot access the run working directory + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + /api/v1/runs/{id}/unarchive: post: operationId: unarchiveRun @@ -3885,6 +4021,26 @@ components: additionalProperties: true additionalProperties: true + RunSupersededByProps: + description: Properties for the `run.superseded_by` audit event emitted on a rewound source run after archive succeeds. + type: object + required: + - new_run_id + - target_checkpoint_ordinal + - target_node_id + - target_visit + properties: + new_run_id: + type: string + target_checkpoint_ordinal: + type: integer + minimum: 1 + target_node_id: + type: string + target_visit: + type: integer + minimum: 1 + EventSeq: description: Assigned sequence number component of a stored event envelope. type: object @@ -4174,6 +4330,8 @@ components: pull_request: type: ["object", "null"] additionalProperties: true + superseded_by: + type: ["string", "null"] pending_interviews: type: object additionalProperties: @@ -4235,6 +4393,84 @@ components: total_usd_micros: type: ["integer", "null"] format: int64 + superseded_by: + type: ["string", "null"] + + ForkRequest: + description: Request body for creating a new run from a source run checkpoint. + type: object + properties: + target: + type: ["string", "null"] + description: Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint. + push: + type: ["boolean", "null"] + description: Whether to push the new run branches. Defaults to true. + + ForkResponse: + description: Response returned after creating a forked run. + type: object + required: + - source_run_id + - new_run_id + - target + properties: + source_run_id: + type: string + new_run_id: + type: string + target: + type: string + + RewindRequest: + description: Request body for creating a replacement run from a source run checkpoint. + type: object + properties: + target: + type: ["string", "null"] + description: Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint. + push: + type: ["boolean", "null"] + description: Whether to push the new run branches. Defaults to true. + + RewindResponse: + description: Response returned after rewind creates a new run. + type: object + required: + - source_run_id + - new_run_id + - target + - archived + properties: + source_run_id: + type: string + new_run_id: + type: string + target: + type: string + archived: + type: boolean + archive_error: + type: ["string", "null"] + + TimelineEntryResponse: + description: Checkpoint timeline entry for a run. + type: object + required: + - ordinal + - node_name + - visit + properties: + ordinal: + type: integer + minimum: 1 + node_name: + type: string + visit: + type: integer + minimum: 1 + run_commit_sha: + type: ["string", "null"] # ── Run Board Schemas ──────────────────────────────────────────────── diff --git a/docs/changelog/2026-04-24.mdx b/docs/changelog/2026-04-24.mdx new file mode 100644 index 000000000..fddd52977 --- /dev/null +++ b/docs/changelog/2026-04-24.mdx @@ -0,0 +1,17 @@ +--- +title: "Server-backed rewind and fork" +date: "2026-04-24" +--- + +## Server-backed rewind and fork + +`fabro rewind` now creates a replacement run at the target checkpoint and archives the source run instead of mutating the source run in place. The command prints the new run ID: + +``` +fabro rewind plan@2 +fabro resume +``` + +The source run records which run superseded it, so archived run history keeps a clear audit trail. Rewind requires the source to be terminal (`succeeded`, `failed`, or `dead`). + +`fabro fork`, `fabro rewind`, and their `--list` modes now use the server API. The server must be able to read the run's recorded working directory. Timeline listing no longer rebuilds a missing metadata branch; if the metadata branch is gone, the timeline is empty until metadata is restored. diff --git a/docs/execution/checkpoints.mdx b/docs/execution/checkpoints.mdx index 74771945b..3eddce7c8 100644 --- a/docs/execution/checkpoints.mdx +++ b/docs/execution/checkpoints.mdx @@ -139,7 +139,7 @@ git show fabro/meta/01JKXYZ...:run.json | jq .checkpoint.current_node ## Rewinding to an earlier checkpoint -If a later stage goes off-track, you can rewind a run to an earlier checkpoint and resume from there instead of restarting the entire workflow: +If a later stage goes off-track, you can rewind a terminal run to an earlier checkpoint and resume from there instead of restarting the entire workflow. Rewind creates a replacement run at the target checkpoint, archives the source run, and prints the new run ID to resume: ```bash # List the checkpoint timeline @@ -149,14 +149,16 @@ fabro rewind --list fabro rewind plan@2 # Resume from the rewound point -fabro resume +fabro resume ``` +The source run must already be terminal (`succeeded`, `failed`, or `dead`). If the source is archived, unarchive it first. If archive fails after the replacement run is created, do not retry `fabro rewind`; archive the source run manually. + See [`fabro rewind`](/reference/cli#fabro-rewind) for the full command reference. ## Forking a run -If you want to explore an alternate path from a checkpoint without losing the original run's history, use `fabro fork` instead of `fabro rewind`. Fork creates a new independent run branching from the target checkpoint — the original run stays intact. +If you want to explore an alternate path from a checkpoint without archiving the original run, use `fabro fork` instead of `fabro rewind`. Fork creates a new independent run branching from the target checkpoint; the original run stays intact. ```bash # List checkpoints @@ -169,7 +171,9 @@ fabro fork plan@2 fabro resume ``` -Use **rewind** when you want to redo a run from an earlier point (destructive — resets the original). Use **fork** when you want to try a different approach while keeping the original run as a reference. +Use **rewind** when a terminal run should be abandoned and replaced from an earlier point. Use **fork** when you want to try a different approach while keeping the original run as a reference. + +`fabro rewind --list`, `fabro fork --list`, `fabro rewind`, and `fabro fork` are server-backed. The server must be able to read the run's recorded `working_directory`; otherwise these commands fail. Timeline listing reads the metadata branch as-is and does not rebuild a missing metadata branch, so a missing branch appears as an empty timeline. See [`fabro fork`](/reference/cli#fabro-fork) for the full command reference. diff --git a/docs/plans/2026-04-23-004-refactor-converge-rewind-into-fork-plan.md b/docs/plans/2026-04-23-004-refactor-converge-rewind-into-fork-plan.md index fc0cb97df..fb8a0c9c8 100644 --- a/docs/plans/2026-04-23-004-refactor-converge-rewind-into-fork-plan.md +++ b/docs/plans/2026-04-23-004-refactor-converge-rewind-into-fork-plan.md @@ -1,7 +1,7 @@ --- title: "refactor: Converge rewind into fork with archive-after" type: refactor -status: active +status: completed date: 2026-04-23 --- @@ -37,19 +37,19 @@ This convergence was brainstormed conversationally on 2026-04-23 (no formal `doc **CLI Behavior (Preserved & Changed)** - R2. `fabro rewind ` archives the source run and returns a new RunId initialized at the target checkpoint. - R3. `fabro fork [target]` continues to leave the source run untouched. -- R4. The `--list` and `--no-push` flags continue to work on both commands with unchanged semantics. +- R4. The `--list` and `--no-push` flags continue to work on both commands. `--no-push` has unchanged semantics (pure flag passthrough). `--list` is preserved as a capability but its implementation moves server-side — the endpoint wraps `build_timeline` (not `build_timeline_or_rebuild`), so a run with a missing metadata branch returns an empty timeline from the server where today's CLI would rebuild it. See Unit 2 for the accepted rebuild regression. **Cleanup & Deletion** - R5. `RunRewound` event, `RunRewoundProps`, `reset_for_rewind`, and the rewind-specific `ensure_not_archived` usage are removed from the codebase. The greenfield constraint lets us delete rather than deprecate. **Regression Prevention** - R6. No regression in timeline resolution (ordinal `@N`, `node`, `node@N`) or parallel-interior handling. -- R7. User-facing documentation that currently teaches in-place-rewind semantics is updated to match the new behavior (see Unit 5). +- R7. User-facing documentation that currently teaches in-place-rewind semantics is updated to match the new behavior (see Unit 6). ## Scope Boundaries - **Not** adding provenance fields (`forked_from: Option`) on forked runs. Covered for rewind by `RunSupersededBy` on the source; adding symmetric provenance on the new run is a separate follow-up covering both fork and rewind. -- **Not** changing fork's CLI surface. `ForkRunInput` and the `fabro fork` CLI continue to work unchanged. Correction from earlier plan text: **`POST /runs/{id}/fork` does not exist today** — fork is CLI-only, operating directly on the local git `Store`. Unit 2 therefore introduces the first git-touching HTTP endpoint in `fabro-server`; there is no ForkResponse shape to align RewindResponse with. +- Fork's surface IS changing: `POST /runs/{id}/fork` is added in Unit 2 alongside `POST /runs/{id}/rewind`, and `fabro fork` becomes a thin CLI wrapper over the new endpoint (mirrors the rewind split). Rationale: consistency (both are mutating git operations) and remote-client support. Unit 2 introduces the first git-touching HTTP endpoints in `fabro-server` and establishes the shared "open git Store from run's `working_directory`" pattern used by rewind, fork, and timeline endpoints. - **Not** changing `build_timeline_or_rebuild` behavior or the rebuild-from-events path. The new `GET /runs/{id}/timeline` endpoint wraps `build_timeline`; it does not modify the underlying function. - **Not** migrating stored `RunRewound` events — greenfield, no deployed instances. - **Not** widening `operations::archive`'s precondition. Rewind inherits the "terminal status required" rule; non-terminal sources (Paused, Blocked, Running, etc.) must be canceled or allowed to finish before they can be rewound. This is a deliberate narrowing from today's behavior — see User Decisions log. @@ -75,15 +75,20 @@ Not needed. This is an internal refactor with no external contract surfaces; tim ## Key Technical Decisions -- **Rewind becomes a server-side composite endpoint, not a CLI orchestration.** Add `POST /runs/{id}/rewind` to the fabro-api server. The handler: - 1. Loads source status from the projection store - 2. Pre-checks terminal state (rejects Running/Paused/Blocked/etc. with a clear 409 Conflict before any git work) - 3. Calls `operations::fork()` synchronously (git branch creation) - 4. Appends `RunSupersededBy { new_run_id }` to the source's event stream (async database write) - 5. Transitions source via `operations::archive()` (reuses existing archive logic) - 6. Returns `{ source_run_id, new_run_id, target, archived: true }` +- **Both rewind and fork become server-side endpoints.** Add `POST /runs/{id}/rewind` AND `POST /runs/{id}/fork` to the fabro-api server. Rationale for moving fork alongside rewind: architectural consistency (both are mutating git operations; keeping one CLI-only and the other server-side creates a split that the second reviewer rightly flagged) and a single HTTP surface for future web-UI consumers. The rewind handler composes fork + archive + event append; the fork handler is a simpler wrapper around `operations::fork()`. Both share the working_directory/git-Store machinery. **Limitation honestly acknowledged:** both endpoints still require the server to have filesystem access to the run's original `working_directory` (a durable field on `RunSpec`). There is no override mechanism in this plan; 501 is a hard failure for runs whose original path isn't server-accessible. A truly-remote scenario (CLI, server, and repo on different hosts) is NOT solved by this plan — that requires a future follow-up (override mechanism, rebuild endpoint, or server-side checkout). - Rationale: user explicitly chose the server-side composite endpoint over CLI orchestration. Benefits: atomicity from the client's perspective, a single audit event on the source (`RunSupersededBy`) answers "why is this archived?" directly, and a future web UI has a single endpoint to call. The async/sync boundary is internal to the handler — `fork()` stays sync; the event append and archive call are async. Pre-check before fork avoids orphan runs on precondition failure; graceful degradation on post-fork archive failure is handled in Unit 3's error path. Does introduce a new endpoint that needs OpenAPI spec + progenitor regeneration. + Rewind handler flow: + 1. Reject if already archived (409 via `reject_if_archived`) + 2. Load source status; reject with 409 Conflict if not terminal (Succeeded/Failed/Dead) + 3. Open git Store from `spec.working_directory` (501 if inaccessible) + 4. spawn_blocking: call `operations::fork()` → new_run_id + 5. Call `operations::archive(source)` FIRST + 6. On archive OK → append `RunSupersededBy { new_run_id }` to source (only-on-archive-success invariant) → return 200 + 7. On archive Err → return 207 Multi-Status with `archived: false, archive_error`; do NOT append RunSupersededBy + + Fork handler flow: steps 3–4 only; **emits no source-side event** (source run is untouched per R3). Returns `{ source_run_id, new_run_id, target }`. Provenance on the new run lives implicitly in its branch contents; a future `RunForkedFrom`-style audit event is Deferred to Follow-Up (applies symmetrically to fork + rewind). + + Benefits: consistent architecture, remote-client support via HTTP, atomicity (rewind), single audit event on source (rewind). Costs: two new endpoints (request/response types, OpenAPI additions, client wrappers), the 501 failure mode applies to both. Does introduce a new boundary in fabro-server: opening a git Store from inside a handler. Establishes the spawn_blocking + per-run-working_directory pattern for future server-side git work. - **Add `RunSupersededBy { new_run_id }` event (supersedes deprecated `RunRewound`).** Lives in `fabro-types::EventBody` and the `fabro-workflow::Event` enum. Emitted on the source run only, by the rewind endpoint, AFTER `operations::archive` succeeds. Projection arm on `run_state.rs` sets `superseded_by: Option` on `RunProjection` so consumers can answer "what replaced this run?" with a single projection read (no event-log replay). Rationale: audit trail was the primary justification for the server-side endpoint; the projection field makes that audit first-class for UI/CLI consumers. @@ -132,6 +137,19 @@ Not needed. This is an internal refactor with no external contract surfaces; tim - **Status code convention (412 vs 409)?** → **Use 409 Conflict** for both archived-source and non-terminal-source rejections. Matches fabro-server's consistent use of `StatusCode::CONFLICT`; error message disambiguates the two cases. No 412 in this plan. - **Server-side timeline/list endpoint?** → **Add `GET /runs/{id}/timeline` to Unit 2.** Matches the mutating-rewind server-side move for web-UI parity; shares the working_directory/git-Store machinery with the rewind endpoint. CLI `--list` calls this endpoint instead of reading local git state. +**Decisions from the third external review (2026-04-24):** +- **Retry-after-partial-success mitigation text?** → **Rewritten to unambiguously forbid rewind-retry.** The risk-table mitigation now explicitly says "run `fabro archive ` manually; do NOT retry `fabro rewind`." Fork mints a fresh RunId each call; retry would orphan another run. +- **Key Technical Decisions handler-steps ordering?** → **Flipped to match the rest of the plan.** High-level summary now shows archive-first, RunSupersededBy-on-success, matching the detailed handler section and the User Decisions log. +- **Timeline rebuild semantics?** → **Accept the regression; document it.** Server endpoint wraps `build_timeline` only, not `build_timeline_or_rebuild`. Documented in Unit 2's timeline endpoint description and in Unit 6 (user docs). Follow-up issue for either server-side rebuild or an explicit rebuild endpoint. +- **`summary_to_api_run_summary` wire-level serializer?** → **Added to Unit 2 file list.** The function at `server.rs:2611` manually builds JSON for `fabro ps` consumers; without editing it, `superseded_by` would exist in types/OpenAPI but never reach the wire. +- **Fork architectural consistency (CLI-only vs server-side)?** → **Move fork server-side too.** Adds `POST /runs/{id}/fork` alongside rewind. Resolves the architectural split the reviewer flagged. Expands Unit 2 scope by one endpoint; Unit 3 now rewrites both `fabro rewind` and `fabro fork` as thin wrappers. Note: the original "supports remote CLI + remote server + remote repo scenarios" framing was overstated (see next review); the server still requires filesystem access to the run's stored `working_directory`. + +**Decisions from the fourth external review (2026-04-24):** +- **501 recovery mechanism?** → **Accept as unrecoverable in this plan.** No override, no CLI-local fallback. The server must have filesystem access to the run's stored `working_directory` (a durable `RunSpec` field); if it can't, 501 is a hard failure. The earlier "checkout-to-local-path-and-retry" guidance was wrong — a caller-side checkout at a different path doesn't change the stored value. The "remote CLI/server/repo on different hosts" benefit claim is walked back. Remote/sandbox recovery is a follow-up concern (override mechanism, rebuild endpoint, or server-side checkout). +- **Fork source-side audit event?** → **None in this plan.** Fork emits no source-side event (source is untouched per R3). Provenance on the new run lives implicitly in its branch contents. `RunForkedFrom` is Deferred to Follow-Up as a symmetric audit event applying to both fork and rewind. +- **Outside-git tests?** → **Rewrite or delete.** `rewind_outside_git_repo_errors` and `fork_outside_git_repo_errors` asserted a local-git precondition that no longer applies (both mutate paths + `--list` now go through the server). Unit 5 instructs either deletion or rewrite to assert a different failure condition. +- **Scope summary + Unit 6 docs under-scoped?** → **Expanded.** Fork's user-facing docs (checkpoints.mdx:159, cli.mdx:582) are added to Unit 6's file list; the shared 501 limitation is documented once. API surface parity section now enumerates all three endpoints, both request/response schema pairs, the RunSummary field, and the new event wiring. + ### Deferred to Implementation - **Exact module visibility of timeline helpers.** Some helpers (`run_commit_shas_by_node`, `find_run_id_by_prefix_opt`) are `pub(crate)` or `pub(super)` today. Reclassify during the move based on who imports from outside `operations::`. @@ -169,32 +187,35 @@ After convergence: fabro rewind @3 fabro fork [@3] | | v v - rewind CLI handler (thin) fork CLI handler - - --list: client.run_timeline(id) - build_timeline (local git) - - mutate: client.rewind_run(id,...) - fork() op (local git) - | - print "Forked X -> Y" - v - POST /runs/{id}/rewind (server) - - reject_if_archived (409 if archived) - - load RunSpec, check terminal status (409 if non-terminal) - - open git Store at spec.working_directory - - spawn_blocking: fork() op <---------- same fork() op - (501 if working_dir inaccessible) - - operations::archive(source) [FIRST] - - on archive OK: append RunSupersededBy [SECOND, only if archive succeeded] - - return 200 (archive ok) | 207 (archive failed; archived:false, no supersede) + rewind CLI (thin) fork CLI (thin) + - --list: client.run_timeline(id) - --list: client.run_timeline(id) + - mutate: client.rewind_run(...) - mutate: client.fork_run(...) + | | + +----- both CLIs are ----------------+ + | thin HTTP clients | + v v + + POST /runs/{id}/rewind (server) POST /runs/{id}/fork (server) + - reject_if_archived (409) - reject_if_archived (409) + - check terminal status (409) - open git Store at working_dir + - open git Store at working_dir - spawn_blocking: fork() op (501 if inacc) + - spawn_blocking: fork() op - return 200 { source, new, target } + (501 if working_dir inacc) + - archive(source) [FIRST] + - on archive OK: append RunSupersededBy [SECOND, only on success] + - return 200 (archive ok) | 207 (archive failed; no supersede) GET /runs/{id}/timeline (server) - - open git Store at spec.working_directory - - spawn_blocking: build_timeline - - return 200 with Vec (501 if working_dir inaccessible) + - open git Store at working_dir (501 if inacc) + - spawn_blocking: build_timeline (NOT _or_rebuild — see Unit 2 note) + - return 200 with Vec ``` The shared `fork()` op is the only code that creates runs, moves refs, or writes metadata snapshots. Rewind's differentiator is a server-side composite endpoint that adds a source-status pre-check, appends `RunSupersededBy` for audit, and archives the source. Fork continues to work exactly as today. ## Implementation Units -- [ ] **Unit 1: Extract timeline module and rename RewindTarget → ForkTarget** +- [x] **Unit 1: Extract timeline module and rename RewindTarget → ForkTarget** **Goal:** Move all timeline-reading logic out of `operations/rewind.rs` into a new `operations/timeline.rs` module. Rename `RewindTarget` to `ForkTarget` in the same pass so downstream callers update once. @@ -214,7 +235,7 @@ The shared `fork()` op is the only code that creates runs, moves refs, or writes **Approach:** - Symbols to move verbatim into `timeline.rs`: `RewindTarget` (renamed `ForkTarget`), `TimelineEntry`, `RunTimeline`, `build_timeline`, `backfill_run_shas`, `run_commit_shas_by_node`, `detect_parallel_interior`, `find_run_id_by_prefix`, `find_run_id_by_prefix_opt`, `load_parallel_map`, `read_projection_at_commit` -- Symbols that stay in `rewind.rs` for Unit 3 deletion: `RewindInput`, `rewind()`, `rewind_to_entry()` +- Symbols that stay in `rewind.rs` (will be rewritten in Unit 2, not deleted): `RewindInput` (repurposed with new fields), `rewind()` (repurposed as async composite). `rewind_to_entry()` is deleted entirely in Unit 4 — it has no equivalent in the new design. - The existing `#[cfg(test)] mod tests` block in `rewind.rs` splits: timeline-parsing and resolution tests (`parse_target_ordinal`, `parse_target_latest_visit`, `build_timeline_simple`, `resolve_latest_visit`, `parallel_interior_detection`, `find_run_id_prefix_match`) move to `timeline.rs`; rewind-specific tests (`rewind_moves_metadata_ref`, `rewind_rejects_archived_runs`) stay for Unit 3 deletion. - Visibility: `find_run_id_by_prefix_opt` is `pub(super)` today — keep `pub(super)` so it's reachable from `rebuild_meta.rs`. Adjust if rustc complains. @@ -232,9 +253,9 @@ The shared `fork()` op is the only code that creates runs, moves refs, or writes - `rg "use .*rewind::(RewindTarget|TimelineEntry|RunTimeline|build_timeline|find_run_id_by_prefix)"` returns no matches — all call sites now import from `timeline`. - Clippy passes: `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings`. -- [ ] **Unit 2: Add `RunSupersededBy` event, `POST /runs/{id}/rewind`, and `GET /runs/{id}/timeline` server endpoints** +- [x] **Unit 2: Add `RunSupersededBy` event and three server endpoints (`POST /runs/{id}/rewind`, `POST /runs/{id}/fork`, `GET /runs/{id}/timeline`)** -**Goal:** Introduce the new audit event and the two server-side endpoints (mutating rewind + read-side timeline). Both endpoints share the "open git Store from run's working_directory" machinery introduced here; solving that once enables the timeline endpoint essentially for free. Web-UI parity requires both. +**Goal:** Introduce the new audit event and the three server-side endpoints. All three share the "open git Store from run's `working_directory` inside `spawn_blocking`" machinery introduced here — solving that once enables all three. Moving both rewind AND fork server-side gives the architecture the consistency the second external review flagged; web UI consumers get a single HTTP surface. This does NOT solve the truly-remote scenario (CLI, server, and repo on different hosts) — the server must still have filesystem access to the run's original `working_directory`. That's a follow-up concern. **Requirements:** R1 (single codepath), R2 (archive source + new RunId) @@ -246,16 +267,29 @@ The shared `fork()` op is the only code that creates runs, moves refs, or writes - Modify: `lib/crates/fabro-workflow/src/event.rs` — add `Event::RunSupersededBy { new_run_id, target_checkpoint_ordinal, target_node_id, target_visit }` variant, logging arm, discriminant, and `EventBody` conversion. Model on the existing `Event::RunRewound` shape (being deleted in Unit 5). - Modify: `lib/crates/fabro-types/src/run_projection.rs` — add `pub superseded_by: Option` field to `RunProjection`, serde-defaulted to `None`. - Modify: `lib/crates/fabro-store/src/run_state.rs` — add `EventBody::RunSupersededBy(props) => self.superseded_by = Some(props.new_run_id);` arm. Single-line projection update; source's archived-status transition still comes from the separate `RunArchived` event per normal lifecycle. -- Modify: `lib/crates/fabro-types/src/run_summary.rs` — add `pub superseded_by: Option` field to `RunSummary` (serde-defaulted). This is the type exposed by list endpoints (`fabro ps`, web list views), so plumbing the field here is what makes the "fabro ps shows superseded" claim honest. +- Modify: `lib/crates/fabro-types/src/run_summary.rs` — add `pub superseded_by: Option` field to `RunSummary` (serde-defaulted). - Modify: the projection→summary mapping (exact file TBD — check `fabro-server` or `fabro-store` for where `RunSummary` is built from `RunProjection`; set `summary.superseded_by = projection.superseded_by`). -- Modify: `docs/api-reference/fabro-api.yaml` around the `RunSummary` schema definition (~line 3943) — add the `superseded_by` property. Also add the new `POST /runs/{id}/rewind` path, `RewindRequest`/`RewindResponse` schemas, `RunSupersededByProps` event schema, `"run.superseded_by"` in the event-name enum, and the new `GET /runs/{id}/timeline` path + `TimelineEntryResponse` schema (see Unit 2 timeline endpoint below). -- Modify: `docs/api-reference/fabro-api.yaml` — add a new `RewindRequest` schema (with `target: Option`, `push: Option` defaulting to true), a new `RewindResponse` schema (`{ source_run_id, new_run_id, target, archived, archive_error?: String }`), and a `POST /runs/{id}/rewind` path. Register `"run.superseded_by"` as an allowable event name in the SSE schema if that enum exists there. -- Create: `pub async fn rewind(...) -> Result` in `lib/crates/fabro-workflow/src/operations/rewind.rs`. This is the file's new contents — replaces the old in-place-rewind function (which is deleted in Unit 4 by virtue of not being reintroduced). Mirror the signature style of `operations::archive`. The function composes `operations::fork` (inside a `spawn_blocking` block) + `operations::archive` + `RunSupersededBy` event append (archive-first-then-supersede, only-on-archive-success). +- Modify: **`lib/crates/fabro-server/src/server.rs` `summary_to_api_run_summary` (line 2611)** — this function manually builds the JSON response shape via `serde_json::json!{...}` and is the wire-level serializer used by `fabro ps` (called from `:2758` and `:4874`). Without adding `"superseded_by": summary.superseded_by` to the emitted JSON, the field exists in storage/types/OpenAPI but never reaches clients. This was missed in the prior plan revision. +- Modify: `docs/api-reference/fabro-api.yaml` — single consolidated set of additions (all three endpoints + shared types): + - `POST /runs/{id}/rewind` path with: + - `RewindRequest` schema: `{ target: Option, push: Option }` (push defaults to true server-side) + - `RewindResponse` schema: `{ source_run_id, new_run_id, target, archived, archive_error?: String }` + - `POST /runs/{id}/fork` path with: + - `ForkRequest` schema: `{ target: Option, push: Option }` (matches current CLI which sends `push: !args.no_push` at `lib/crates/fabro-cli/src/commands/run/fork.rs:42`) + - `ForkResponse` schema: `{ source_run_id, new_run_id, target }` (no archived field — fork doesn't archive) + - `GET /runs/{id}/timeline` path with `TimelineEntryResponse` schema matching today's `TimelineEntry` shape (ordinal, node_name, visit, run_commit_sha). + - `RunSupersededByProps` event schema and `"run.superseded_by"` added to the SSE event-name enum. + - `superseded_by: Option` property added to the `RunSummary` schema (around line 3943). +- Create: `pub async fn rewind(&Database, &GitStoreFactory, &RewindInput, Option) -> Result` in `lib/crates/fabro-workflow/src/operations/rewind.rs`. This is the file's new contents — replaces the old in-place-rewind function. Mirror the signature style of `operations::archive`. The function composes `operations::fork` (inside a `spawn_blocking` block) + `operations::archive` + `RunSupersededBy` event append (archive-first-then-supersede, only-on-archive-success). +- Rewrite the `RewindInput` struct in the same file. New fields: `{ run_id: RunId, target: Option, push: bool }`. Note the removed field: `current_status` is gone — the composite op loads status from the projection itself. The type name is preserved for call-site stability. +- Add a `RewindOutcome` enum: `Full { new_run_id: RunId, archived: bool }` (archive succeeded) and `Partial { new_run_id: RunId, archive_error: String }` (archive failed post-fork). Handler maps to 200/207 respectively. - Create: server handler in `lib/crates/fabro-server/src/server.rs` — thin `async fn rewind_run(...)` delegator into `operations::rewind`, matching the 4-line pattern of `archive_run` (line 6448). Add route `.route("/runs/{id}/rewind", post(rewind_run))` next to `archive_run` / `unarchive_run` (see lines 1086-1087). -- Create: `pub async fn timeline(...) -> Result, Error>` in `lib/crates/fabro-workflow/src/operations/timeline.rs` (the new module from Unit 1). This is an async wrapper around the existing sync `build_timeline` — opens the git Store from the run's working_directory (same pattern as the rewind endpoint) inside `spawn_blocking`. +- Create: `pub async fn fork(...)` in a new `lib/crates/fabro-workflow/src/operations/fork_op.rs` or repurpose existing `operations/fork.rs` into an async wrapper. The sync `fork()` function becomes the inner (called inside `spawn_blocking`) and the new async `fork()` handles store opening + working_directory lookup + error mapping. +- Create: server handler `async fn fork_run(...)` in `server.rs`. Add route `.route("/runs/{id}/fork", post(fork_run))`. Returns `{ source_run_id, new_run_id, target }`. Status codes: 200 success, 400 bad target, 404 unknown run, 409 source archived, 501 working_directory inaccessible. **Fork emits no source-side event** (source is untouched per R3); provenance lives in the new run's branch contents only. A symmetric `RunForkedFrom` audit event is Deferred to Follow-Up. +- Create: `pub async fn timeline(...) -> Result, Error>` in `lib/crates/fabro-workflow/src/operations/timeline.rs` (the new module from Unit 1). Async wrapper around the existing sync `build_timeline` — opens the git Store from the run's working_directory inside `spawn_blocking`. **Known regression accepted by this plan:** the server endpoint wraps `build_timeline` only, NOT `build_timeline_or_rebuild` (the rebuild-from-events fallback today's CLI uses at `lib/crates/fabro-workflow/src/operations/rebuild_meta.rs:124`). A run with a missing metadata branch will show an empty timeline from the server endpoint where today's CLI would rebuild it. Document in Unit 6 (user docs). Follow-up: either move rebuild server-side too, or add a `POST /runs/{id}/rebuild` endpoint users can call explicitly when they hit "no checkpoints found." - Create: server handler in `lib/crates/fabro-server/src/server.rs` — thin `async fn run_timeline(...)` delegator. Add route `.route("/runs/{id}/timeline", get(run_timeline))`. Status codes: 200 with `Vec` on success; 404 for unknown run; 501 for inaccessible working_directory. - Modify: `lib/crates/fabro-workflow/src/event.rs` — append_event support for `RunSupersededBy` via existing event append pathway. -- Modify: `lib/crates/fabro-client/src/client.rs` — add hand-written wrappers for both new endpoints (`rewind_run`, `run_timeline`) following the archive_run wrapper pattern. +- Modify: `lib/crates/fabro-client/src/client.rs` — add hand-written wrappers for all three new endpoints (`rewind_run`, `fork_run`, `run_timeline`) following the archive_run wrapper pattern. - Test: unit tests for `operations::rewind` and `operations::timeline` in their respective test modules (axum-free, covers composite branches including the 501/working_directory-inaccessible path); plus thin handler tests for HTTP-layer behavior following existing archive/unarchive test patterns. **Approach:** @@ -263,7 +297,7 @@ The shared `fork()` op is the only code that creates runs, moves refs, or writes 1. Parse run ID from path; reject if archived (via `reject_if_archived`, mirrors archive/unarchive). 2. Read body → `RewindRequest { target: Option, push: Option }`. 3. Load source status from projection; reject with **409 Conflict** if not `Succeeded/Failed/Dead` (matches fabro-server's consistent use of `StatusCode::CONFLICT` for state preconditions — see multiple callers in `server.rs`). Include the canonical precondition message. - 4. **Open the git `Store` by looking up the run's working_directory.** `AppState` has no global `repo_path` — confirmed by grep: `pub struct AppState` at `server.rs:539` has no repo field. The handler loads the run's `RunSpec` from the projection store, reads `spec.working_directory` (`lib/crates/fabro-types/src/run.rs:58`), and opens a git `Store` at that path. **New precondition:** the server process must have filesystem access to the run's `working_directory`. If the path doesn't exist, isn't a git repo, or isn't accessible (e.g., the run was launched in a Daytona sandbox or on a remote worker whose filesystem isn't shared with the server), return **501 Not Implemented** with a message directing the user to the CLI rewind command for non-local runs. Exact error shape deferred to implementation, but this failure mode is documented in the error-path test scenarios. The Store must be Send + 'static so the whole git block can run inside `spawn_blocking`. + 4. **Open the git `Store` by looking up the run's working_directory.** `AppState` has no global `repo_path` — confirmed by grep: `pub struct AppState` at `server.rs:539` has no repo field. The handler loads the run's `RunSpec` from the projection store, reads `spec.working_directory` (`lib/crates/fabro-types/src/run.rs:58`), and opens a git `Store` at that path. **New precondition:** the server process must have filesystem access to the run's `working_directory`. If the path doesn't exist, isn't a git repo, or isn't accessible (e.g., the run was launched in a Daytona sandbox or on a remote worker whose filesystem isn't shared with the server), return **501 Not Implemented** with an honest error message explaining the limitation — NOT a "use the CLI" fallback suggestion (the CLI is a thin wrapper around this same endpoint; there is no CLI-local rewind/fork implementation after Unit 3). The Store must be Send + 'static so the whole git block can run inside `spawn_blocking`. 5. **Wrap steps 5–6 in `tokio::task::spawn_blocking`** — `operations::fork` does sync libgit2 work including potential remote push, which can block for seconds. Precedent: `spawn_blocking` is the established pattern in `server.rs` (lines 1291, 1331, 1674, 1711, 4564). Running `fork()` directly on the async runtime stalls Tokio workers under load. The spawn_blocking return should carry the new_run_id back to async context. 6. Inside spawn_blocking: build timeline (sync), resolve target (`None` defaults to latest checkpoint), call `operations::fork(...)` → `new_run_id`. 7. Back on the async runtime: call `operations::archive(&state.store, &id, actor)` FIRST. @@ -322,7 +356,7 @@ struct RewindResponse { - Edge case: source status changes between pre-check and archive (TOCTOU race, simulate with a concurrent event append) → archive returns `Err(Precondition)`; endpoint returns 207 (same shape as transport failure), NOT 500. Source event log does NOT carry `RunSupersededBy`. - Edge case: `RunSupersededBy` append fails after archive succeeds (simulate storage error) → response is still 200 with `archived: true`; source is cleanly archived but provenance is missing in its event log. Log the append failure prominently; this is a repairable degradation. - Error path: source already archived → `reject_if_archived` returns 409 before handler business logic runs; no fork attempt. -- Error path: source `working_directory` is not accessible (simulate by passing a path the server can't stat) → 501 Not Implemented with guidance to use the CLI rewind command. +- Error path: source `working_directory` is not accessible (simulate by passing a path the server can't stat) → 501 Not Implemented with an honest error message explaining the limitation (no "use the CLI" fallback — the CLI is now a thin HTTP wrapper). - Integration: full CLI → server → git path in a CLI-level or scenario test (covered in Unit 5). **Verification:** @@ -331,30 +365,42 @@ struct RewindResponse { - Conformance test `fabro-server` run-catches-spec-drift (per CLAUDE.md API workflow) passes. - `rg -n 'run\.superseded_by' lib/crates/ docs/api-reference/` finds matching wire identifiers in at least `fabro-types`, `fabro-workflow`, and `fabro-api.yaml`. -- [ ] **Unit 3: Rewrite `fabro rewind` CLI as a thin wrapper around the new endpoint** +- [x] **Unit 3: Rewrite both `fabro rewind` and `fabro fork` CLIs as thin wrappers around the new server endpoints** -**Goal:** Replace the current in-place rewind logic in the CLI handler with a single call to the new server endpoint, plus timeline-listing and output formatting. Output text continues to use "rewind" vocabulary. +**Goal:** Replace both CLIs' local git logic with calls to the new server endpoints, plus timeline-listing and output formatting. Output text continues to use "rewind"/"fork" vocabulary. Both CLI commands become small adapters: parse args, call the endpoint, render the response. -**Requirements:** R2, R4 (`--list` / `--no-push` unchanged) +**Requirements:** R2 (rewind new behavior), R3 (fork unchanged user-facing behavior), R4 (`--no-push` unchanged; `--list` preserved as a capability with the accepted rebuild regression) -**Dependencies:** Units 1 and 2 (needs `ForkTarget` in scope, needs the server endpoint and generated client method). +**Dependencies:** Units 1 and 2 (needs `ForkTarget`, all three server endpoints, and generated client methods). **Files:** - Modify: `lib/crates/fabro-cli/src/commands/run/rewind.rs` (full rewrite) -- Modify: `lib/crates/fabro-client/src/client.rs` — add hand-written wrapper `pub async fn rewind_run(&self, run_id: &RunId, req: &RewindRequest) -> Result` matching the style of existing `archive_run`/`unarchive_run` wrappers around the progenitor-generated call. -- Test: `lib/crates/fabro-cli/tests/it/cmd/rewind.rs` (assertions rewritten in Unit 5) +- Modify: `lib/crates/fabro-cli/src/commands/run/fork.rs` (full rewrite — becomes a thin wrapper over `client.fork_run`) +- Modify: `lib/crates/fabro-client/src/client.rs` — wrappers added in Unit 2 (`rewind_run`, `fork_run`, `run_timeline`); Unit 3 just consumes them. +- Test: `lib/crates/fabro-cli/tests/it/cmd/rewind.rs` and `lib/crates/fabro-cli/tests/it/cmd/fork.rs` (assertions rewritten in Unit 5) -**Approach:** -- Mirror the shape of `lib/crates/fabro-cli/src/commands/run/fork.rs` for origin validation, but: - - `--list` path: call `client.run_timeline(&run_id)` (the new endpoint) instead of reading local git state. This matches the mutating rewind's server-side move. Falls back gracefully with a helpful message if the endpoint returns 501 — but the common case (local runs) works through the server. - - Non-list path: parse target, build `RewindRequest`, call `client.rewind_run(&run_id, &req)`, handle response. -- Delete the helpers `reset_rewound_run_state`, `restored_checkpoint_event`, `run_event` (and their `RunRewoundProps`/`CheckpointCompletedProps`/`RunSubmittedProps` imports). They have no consumer after this unit. -- Keep `print_timeline` and `timeline_entries_json` — `fork.rs` imports them; they now format data that arrived from the server, not data built locally. -- Output text format: `"Rewound {source[:8]}; new run {new[:8]}"` followed by `"To resume: fabro resume {new[:8]}"`. On HTTP 207 (`archived == false`), also print `"Warning: source not archived: {archive_error}. Run `fabro archive {source}` to finish."` so the user knows the source is still terminal-but-not-archived and how to clean up. -- JSON output: echo `response` shape plus the HTTP status code so scripts can branch on 200 vs 207 without re-parsing. -- **Retry posture: single-shot.** The CLI does NOT auto-retry `POST /rewind` on network error, timeout, or 5xx. On any non-response failure, print `"Network error during rewind. Check server state with 'fabro ps' before retrying — the rewind may have succeeded."`. Rationale: fork mints a fresh RunId each call, so naive retry creates orphans. See "Retry semantics" key decision. -- CLI no longer calls `fork()` directly; that's entirely server-side now. -- Git `Store` access stays CLI-side for the `--list` path (timeline display reads local git state). Origin validation (`ensure_matching_repo_origin`) still runs client-side. +**Approach — shared for both commands:** +- Delete the CLI-side helpers `reset_rewound_run_state`, `restored_checkpoint_event`, `run_event` (and their `RunRewoundProps`/`CheckpointCompletedProps`/`RunSubmittedProps` imports) from `rewind.rs`. They have no consumer after this unit. +- Keep `print_timeline` and `timeline_entries_json` — both CLIs use them; they now format data that arrived from the server, not data built locally. +- Both CLIs no longer call `operations::fork` or any git op directly; all git work is server-side. +- `--list` on both commands calls `GET /runs/{id}/timeline` rather than reading local git state. +- Origin validation (`ensure_matching_repo_origin`) still runs client-side when the CLI has local repo access; when it doesn't (fully-remote CLI), origin validation is skipped and the server's view is trusted. +- **Retry posture: single-shot for both mutate paths.** Neither `fabro rewind` nor `fabro fork` auto-retries on network error, timeout, or 5xx. On non-response failure, print a "check server state" message. Rationale: both endpoints mint fresh RunIds on each call; naive retry creates orphans. + +**Approach — `fabro rewind` specifics:** +- `--list`: call `client.run_timeline(&run_id)` and format with `print_timeline` / `timeline_entries_json`. +- Mutate: parse target, build `RewindRequest { target, push: !args.no_push }`, call `client.rewind_run(&run_id, &req)`, handle 200 vs 207. +- Output text on 200: `"Rewound {source[:8]}; new run {new[:8]}"` followed by `"To resume: fabro resume {new[:8]}"`. +- Output text on 207: in addition to the above, print `"Warning: source not archived: {archive_error}. Run `fabro archive {source}` to finish."` — single-shot retry policy applies; do NOT auto-retry rewind. +- JSON output: echo the `RewindResponse` shape plus the HTTP status code so scripts can branch on 200 vs 207 without re-parsing. + +**Approach — `fabro fork` specifics:** +- `--list`: call `client.run_timeline(&run_id)` (same endpoint as rewind's --list path). Format identically. +- Mutate: parse target, build `ForkRequest { target, push: !args.no_push }`, call `client.fork_run(&run_id, &req)`, handle response. +- Output text on 200: preserve today's fork message pattern — `"Forked {source[:8]} -> {new[:8]}"` followed by `"To resume: fabro resume {new[:8]}"` (matches current snapshot in `tests/it/cmd/fork.rs:57`). +- JSON output: echo the `ForkResponse` shape `{ source_run_id, new_run_id, target }` plus the HTTP status code. +- No partial-success case — fork doesn't archive, so there's no equivalent of the 207 path. Error paths: 400 bad target, 404 unknown run, 409 source archived, 501 working_directory inaccessible. +- `--no-push` translates to `push: false` in the request body (today's CLI does the same via `push: !args.no_push` at `fork.rs:42`); server honors it. **Patterns to follow:** - `lib/crates/fabro-cli/src/commands/run/fork.rs` — same shape for `--list` path. @@ -371,16 +417,28 @@ struct RewindResponse { - Error path: source run is still running or paused → server returns **409 Conflict** with "must be terminal" message; CLI prints it clearly; no new run anywhere. - Error path: source already archived → server returns 409 Conflict; CLI prints "run is archived; run `fabro unarchive` first and retry"; no new run. - Edge case: server returns 207 Multi-Status with `archived: false, archive_error: "..."` → CLI prints the new RunId, the archive-failure warning with the `fabro archive ` hint, and exits 0 so scripts can still pick up the new RunId. -- Edge case: server returns 501 Not Implemented (working_directory inaccessible) → CLI prints a clear message suggesting checkout-to-local-path-and-retry; exits non-zero. +- Edge case: server returns 501 Not Implemented (working_directory inaccessible) → CLI prints a clear error: `"Server cannot access this run's working_directory. This is a hard limitation in the current release; a future version may support an override or rebuild mechanism."` Exits non-zero. No retry guidance — retrying won't help. - Edge case: network error or timeout during POST /rewind → CLI exits non-zero with the "check server state" message; does NOT auto-retry. -- Integration: after `rewind @2`, `fabro ps` shows source as Archived and the new RunId present and resumable. +- Integration: after `rewind @2`, `fabro ps` shows source as Archived with `superseded_by = new_run_id`, and the new RunId is resumable. + +**Test scenarios — `fabro fork`:** +- Happy path: `fabro fork @2 --no-push` exits 0, stderr contains "Forked {source} -> {new}" and "To resume: fabro resume {new}" — matches today's snapshot at `tests/it/cmd/fork.rs:57`; request body sent to server has `push: false`. +- Happy path (default target): `fabro fork ` with no target resolves to latest checkpoint server-side; CLI renders the response identically. +- Happy path (JSON): `--json` emits `{source_run_id, new_run_id, target}` (no `archived` field) plus HTTP status code. +- Edge case: `fabro fork --list` prints the timeline via `GET /runs/{id}/timeline` (same behavior as rewind --list); source unchanged. +- Edge case: `--no-push` translates into `push: false` in the ForkRequest body; server honors it. +- Error path: target `@99` out of range → server returns 400; CLI prints the error; no new run. +- Error path: source already archived → server returns 409 Conflict; CLI prints "run is archived; run `fabro unarchive` first" message; no new run. +- Edge case: server returns 501 Not Implemented (working_directory inaccessible) → CLI prints the same hard-limitation error as rewind; exits non-zero. +- Edge case: network error or timeout during POST /fork → CLI exits non-zero with "check server state" message; does NOT auto-retry (same reasoning as rewind — fork mints a fresh RunId per call). +- Integration: after `fork @2`, source run is unchanged (no `RunSupersededBy`, no archive); new RunId is resumable via `fabro resume `. **Verification:** - `cargo nextest run -p fabro-cli` passes with Unit 5's updated assertions. -- `fabro rewind --help` output unchanged (args struct untouched). -- The CLI-snapshot test `rewind_target_updates_metadata_and_resume_hint` passes against new output text. +- Both `fabro rewind --help` and `fabro fork --help` output unchanged (args structs untouched). +- The CLI-snapshot tests `rewind_target_updates_metadata_and_resume_hint` and `fork_latest_prints_new_run_and_resume_hint` pass against new/unchanged output text. -- [ ] **Unit 4: Delete RunRewound event, in-place rewind op, and projection reset plumbing** +- [x] **Unit 4: Delete RunRewound event, in-place rewind op, and projection reset plumbing** **Goal:** Remove every code path that existed solely to support in-place rewind. Compile cleanly. Note: `rewind.rs` the file STAYS — Unit 2 replaced its contents with the new composite `operations::rewind` function. This unit deletes the old in-place `rewind()` body and associated wire-contract types, not the file. @@ -389,8 +447,8 @@ struct RewindResponse { **Dependencies:** Units 1, 2, and 3 (nothing should import `rewind()` or reference `RunRewound` after those units; this unit verifies and deletes). **Files:** -- Modify: `lib/crates/fabro-workflow/src/operations/rewind.rs` — confirm the in-place `rewind()` function, `RewindInput`, `rewind_to_entry`, and the `ensure_not_archived` precondition call are all gone. After Unit 2 the file contains only the new composite `pub async fn rewind(...)` and its helpers. -- Modify: `lib/crates/fabro-workflow/src/operations/mod.rs` — update the `rewind::` re-export block to expose the new composite function (`pub use rewind::{rewind, RewindInput, RewindOutcome};`) rather than the old one. Old `RewindTarget`/`TimelineEntry`/`RunTimeline`/`build_timeline`/`find_run_id_by_prefix` re-exports move to `timeline::` per Unit 1. +- Modify: `lib/crates/fabro-workflow/src/operations/rewind.rs` — confirm the OLD in-place `rewind()` body, the OLD `RewindInput.current_status` field, `rewind_to_entry`, and the `ensure_not_archived` precondition call are all gone. After Unit 2 the file contains only the NEW composite `pub async fn rewind(...)` and its helpers. The `RewindInput` type name survives — its fields are rewritten to `{ run_id, target, push }` (no `current_status`) in Unit 2. +- Modify: `lib/crates/fabro-workflow/src/operations/mod.rs` — update the `rewind::` re-export block to expose the new composite function (`pub use rewind::{rewind, RewindInput, RewindOutcome};`). `RewindInput` is the same name as before but a different struct shape. Old `RewindTarget`/`TimelineEntry`/`RunTimeline`/`build_timeline`/`find_run_id_by_prefix` re-exports move to `timeline::` per Unit 1. - Modify: `lib/crates/fabro-workflow/src/event.rs` — delete `Event::RunRewound` variant, its logging arm (~line 613), its `"run.rewound"` discriminant (~line 1178), and its `EventBody::RunRewound` conversion (~line 1586) - Modify: `lib/crates/fabro-types/src/run_event/mod.rs` — delete `EventBody::RunRewound(RunRewoundProps)` variant (~line 128), its `"run.rewound"` discriminant (~line 393), AND the `"run.rewound"` string-match arm at line 524. Confirmed sites: `rg -n 'run\.rewound|RunRewound' lib/crates/fabro-types/src/run_event/mod.rs` returns lines 127, 128, 393, 524 — all four must go. - Modify: `lib/crates/fabro-types/src/run_event/run.rs` — delete `pub struct RunRewoundProps` (~lines 90-99) @@ -417,9 +475,9 @@ struct RewindResponse { - `cargo build --workspace` succeeds. - `cargo nextest run --workspace` passes. - `rg "RunRewound|reset_for_rewind|RunRewoundProps"` returns zero hits. -- `rg "operations::rewind"` returns zero hits. +- `rg "\\brewind_to_entry\b|ensure_not_archived.*rewind|current_status.*RewindInput"` returns zero hits (the OLD in-place-rewind internals are gone). Notes: `operations::rewind` itself stays — it's the path of the NEW async composite op. `RewindInput` the type name also stays but with new fields `{ run_id, target, push }` — no `current_status`. -- [ ] **Unit 5: Update tests for new rewind semantics** +- [x] **Unit 5: Update tests for new rewind semantics** **Goal:** Rewrite tests that asserted old in-place rewind behavior to assert the new fork-and-archive semantics. Split the recovery scenario into two focused scenarios. Delete tests for behavior that no longer exists. @@ -429,6 +487,7 @@ struct RewindResponse { **Files:** - Modify: `lib/crates/fabro-cli/tests/it/cmd/rewind.rs` (rewrite assertions; preserve `--help` snapshot structure) +- Modify: `lib/crates/fabro-cli/tests/it/cmd/fork.rs` (rewrite assertions — CLI is now a thin HTTP wrapper; tests mock the server endpoint and assert the CLI renders responses correctly; drop local-git assertions) - Modify: `lib/crates/fabro-cli/tests/it/cmd/resume.rs` — two tests use the old `rewind ... resume ` (same RunId) pattern and will break under new semantics: - `resume_rewound_run_succeeds` (~line 61) — rewrite to capture the new RunId from rewind stderr/JSON and resume *that* id. - `resume_detached_does_not_create_launcher_record` (~line 125) — same pattern; same rewrite. @@ -439,8 +498,8 @@ struct RewindResponse { **Approach:** - In `tests/it/cmd/rewind.rs`: - - `rewind_outside_git_repo_errors` — unchanged. - - `rewind_list_prints_timeline_for_completed_git_run` — unchanged (list path unmodified). + - `rewind_outside_git_repo_errors` — **rewrite or delete.** The current test (`rewind.rs:41`) exercises `fabro rewind --list` outside a git repo and expects a local-git-absent failure. After Unit 3, `--list` calls `GET /runs/{id}/timeline` over HTTP; outside-git is no longer an error for the list path. Decide: (a) delete the test if no meaningful assertion remains, or (b) rewrite to assert the CLI error message surfaced when the server is unreachable / the run ID is unknown. Same applies to `fork_outside_git_repo_errors` at `tests/it/cmd/fork.rs:41` — the fork mutate path is also now server-side, so outside-git isn't the failure mode anymore. + - `rewind_list_prints_timeline_for_completed_git_run` — rewrite to mock `GET /runs/{id}/timeline` rather than reading local git; assert the CLI renders the server response identically to today's output. - `rewind_target_updates_metadata_and_resume_hint` — rewrite. New assertions: (1) command succeeds; (2) stderr includes "Rewound" and "To resume: fabro resume"; (3) the resume hint points at a new RunId (not `setup.run.run_id`); (4) source run is now Archived. Drop the old assertion that the source's metadata ref moved. - `rewind_preserves_event_history_and_clears_terminal_snapshot_state` — delete. This test asserted `run.rewound` + `checkpoint.completed` + `run.submitted` event append and projection reset, all of which no longer happen. Replace with a test that asserts BOTH sides explicitly: (1) source event log gains exactly two new events in order: `run.archived` then `run.superseded_by` (matches the archive-first ordering and the only-on-archive-success rule); (2) the new run's event log contains the expected init events in order (`run.submitted`, `checkpoint.completed` from the target checkpoint), with the exact expected event count. The original test's event-count-delta assertion is the kind of coverage that catches helper-function run_id-mixup bugs; preserve that discipline in the rewrite. - In `tests/it/scenario/recovery.rs`: @@ -455,12 +514,25 @@ struct RewindResponse { **Test scenarios:** - Happy path: `rewind_target_creates_new_run_and_archives_source` — run rewind, assert new RunId in output, assert source status is Archived, assert source's event log gains exactly two events in order: (1) `RunArchived`, (2) `RunSupersededBy` (archive-first ordering). Assert source `RunProjection.superseded_by == Some(new_run_id)` and `RunSummary.superseded_by == Some(new_run_id)`. Assert new run has init + checkpoint events. -- Edge case: `rewind_list_unchanged` — `--list` still prints timeline without side effects (no server call). +- Edge case: `rewind_list_calls_timeline_endpoint` — `--list` calls `GET /runs/{id}/timeline`; no mutation, no local git access required. - Edge case: `rewind_with_no_target_prints_timeline` — no-target invocation behaves like `--list`. - Edge case: `rewind_no_push_skips_remote_but_still_archives` — `--no-push` translates to `push: false` on the request; source is still archived via the server endpoint. - Error path: `rewind_target_out_of_range_does_not_archive` — bad target → server 400; source remains in original (non-archived) status; no new run branches created. - Error path: `rewind_non_terminal_source_rejected` — source is still running/paused → server 409 Conflict with "must be terminal" message; no new run. -- Edge case: `rewind_graceful_degradation_on_archive_failure` — simulate archive failure (e.g., by archiving the source manually first so the precondition short-circuits) → CLI prints new RunId with warning; exit code 0. +- Edge case: `rewind_graceful_degradation_on_archive_failure` — simulate archive failure via fault injection on the archive call path (NOT by pre-archiving the source, which `reject_if_archived` blocks at handler step 1). Expected: server returns 207; CLI prints new RunId with warning; exit code 0; source event log does NOT gain `RunSupersededBy` (only-on-archive-success invariant). +- Error path: `rewind_unknown_run_mutate` — `fabro rewind @2` fails at `resolve_run` (prefix/id lookup before the main operation, CLI pattern at `commands/run/rewind.rs:40`). CLI prints "run not found: " and exits non-zero. No server mutate call is made. +- Error path: `rewind_unknown_run_list` — `fabro rewind --list` fails at the same resolution step; CLI prints the same "run not found" message; no timeline endpoint call. +- Happy path: `fork_cli_creates_new_run_and_prints_hint` — `fabro fork @2 --no-push` exits 0, stderr matches the "Forked X -> Y" pattern today's test at `tests/it/cmd/fork.rs:57` asserts; source unchanged (no events gained); new RunId is resumable. +- Happy path: `fork_cli_default_target_resolves_to_latest` — `fabro fork ` (no target) resolves server-side to latest checkpoint; CLI output identical to the explicit `@N` case. +- Happy path (JSON): `fork_cli_json_output` — `--json` emits `{source_run_id, new_run_id, target}` (no `archived` field, distinguishing it from rewind's response). +- Edge case: `fork_cli_list_calls_timeline_endpoint` — `fabro fork --list` calls the shared timeline endpoint; no mutation. +- Edge case: `fork_cli_no_push_passthrough` — `--no-push` sends `push: false` in the ForkRequest body (mirrors today's `push: !args.no_push` behavior). +- Error path: `fork_cli_target_out_of_range` — bad target → server 400; CLI prints error; no new run. +- Error path: `fork_cli_archived_source_rejected` — source already archived → server 409; CLI prints "unarchive first" message. +- Error path: `fork_501_when_working_directory_inaccessible` — server can't reach the run's working_directory → 501 Not Implemented; CLI prints the same hard-limitation error as rewind; exits non-zero. +- Error path: `fork_cli_network_error_does_not_retry` — network failure during POST /fork → CLI exits non-zero with "check server state" message; no auto-retry. +- Error path: `fork_cli_unknown_run_mutate` — `fabro fork @2` fails at `resolve_run` (same CLI resolution pattern at `commands/run/fork.rs:18`); CLI prints "run not found: " and exits non-zero. No /fork call is made. +- Error path: `fork_cli_unknown_run_list` — `fabro fork --list` fails at the same resolution step; same error message; no timeline endpoint call. - Integration: `recovery.rs` scenarios above — rewind then resume the new RunId; fork chain rebuilds metadata. **Verification:** @@ -468,7 +540,7 @@ struct RewindResponse { - `cargo insta pending-snapshots` is empty after acceptance. - No test references `RunRewound`, `reset_for_rewind`, or `ensure_not_archived` in a rewind-specific context. -- [ ] **Unit 6: Update user-facing documentation for new rewind semantics** +- [x] **Unit 6: Update user-facing documentation for new rewind semantics** **Goal:** Replace the "in-place destructive rewind" mental model in shipped user docs with the "rewind produces a new run from a prior checkpoint and archives the source" model. Add a changelog entry so users learn of the semantic shift. @@ -477,14 +549,21 @@ struct RewindResponse { **Dependencies:** Units 1-5 complete and merged. Docs should describe the shipped behavior, not the planned behavior. **Files:** -- Modify: `docs/execution/checkpoints.mdx` (lines 140-159 describe rewind; rewrite "resume from the same RunId" flow to "resume from the new RunId printed by rewind"; rewrite fork-vs-rewind contrast to "fork keeps both, rewind archives the source") -- Modify: `docs/reference/cli.mdx` (rewind CLI reference entry around line 584; remove "resets the original run in place" language; document the new output format including the `source_run_id` + `new_run_id` JSON fields) -- Create: `docs/changelog/.mdx` — single entry announcing that `fabro rewind` now creates a new run and archives the source, replacing in-place rewind. Include a migration note for any scripts that parse rewind output. +- Modify: `docs/execution/checkpoints.mdx` — two sections need updates: + - Lines 140-159 (rewind): rewrite "resume from the same RunId" flow to "resume from the new RunId printed by rewind"; rewrite fork-vs-rewind contrast to "fork keeps both, rewind archives the source." + - Line 159 (fork): fork now uses server-side HTTP; the "local independent copy" framing still holds semantically, but the new --list path goes through the server; note the shared 501 limitation for inaccessible working_directories. +- Modify: `docs/reference/cli.mdx` — three sections need updates: + - Rewind CLI reference entry around line 584: remove "resets the original run in place" language; document new output format (`source_run_id` + `new_run_id` JSON fields, 207 partial-success response). + - Fork CLI reference entry around line 582: update for server-backed behavior — `--list` now calls the server timeline endpoint; 501 is possible; no longer requires a local git repo for --list. + - Shared 501 limitation note applicable to both commands. +- Create: `docs/changelog/.mdx` — single entry announcing: (1) `fabro rewind` now creates a new run and archives the source (replacing in-place rewind); (2) both `fabro rewind` and `fabro fork` now call server endpoints (architectural change; same output for local runs, but runs whose `working_directory` the server can't see return 501); (3) new `RunSupersededBy` event + `superseded_by` projection field. Include a migration note for any scripts that parse rewind output. **Approach:** - Audit first: `rg -i "rewind|rewound" docs/ apps/` to confirm the file list. Ignore changelog history entries (they correctly describe behavior at their own date). - Keep the `fabro rewind` CLI as the documented verb for "try from earlier checkpoint" — the semantic-name preservation is deliberate. Update the explanation of what it does, not the name. - Mention in the docs that the source run is archived (not lost) and can be unarchived with `fabro unarchive` if needed. +- Document the accepted timeline regression: runs with a missing metadata branch will show an empty timeline from the server endpoint. If users need the old CLI rebuild behavior, a follow-up issue tracks either server-side rebuild or an explicit `POST /runs/{id}/rebuild` endpoint. +- Document the 501 failure mode honestly: when the server can't access a run's `working_directory` (remote worker, sandbox, missing path), rewind/fork/timeline all return 501. This is a hard limitation — there is no retry or workaround in this release. A follow-up may add an override or rebuild mechanism. **Patterns to follow:** - Existing `docs/changelog/*.mdx` format for the new entry. @@ -500,35 +579,35 @@ struct RewindResponse { ## System-Wide Impact -- **Interaction graph:** Rewind is now a single HTTP call from the CLI (`POST /runs/{id}/rewind`) that atomically composes fork + archive server-side. Pre-check before fork eliminates the precondition half-success case; transport-level archive failure is handled by the endpoint returning `archived: false, archive_error: ...` so the CLI can surface the warning while still delivering the new RunId. -- **Error propagation:** Fork errors surface as server 400. Archived source returns 409 (via `reject_if_archived`). Non-terminal source returns 409 (pre-check in handler; disambiguated in error body). Inaccessible `working_directory` returns 501. Post-archive Precondition errors (concurrent-mutation race) return 207 Multi-Status, same as transport failures — NOT 500. Archive errors are degradations, not bugs. +- **Interaction graph:** Rewind AND fork are now HTTP calls from the CLI (`POST /runs/{id}/rewind`, `POST /runs/{id}/fork`); `--list` is also HTTP (`GET /runs/{id}/timeline`). The rewind endpoint atomically composes fork + archive server-side. Pre-check before fork eliminates the precondition half-success case; transport-level archive failure is handled by the endpoint returning `archived: false, archive_error: ...` so the CLI can surface the warning while still delivering the new RunId. +- **Error propagation:** Bad targets surface as server 400 on both `/rewind` and `/fork`. Archived source returns 409 on both (via `reject_if_archived`). **Rewind only**: non-terminal source returns 409 (rewind's pre-check in handler; disambiguated in error body). Fork does NOT pre-check terminal status — it preserves today's behavior of forking any non-archived source regardless of run status (per R3). Inaccessible `working_directory` returns 501 on all three endpoints (rewind/fork/timeline). **Rewind only**: post-archive Precondition errors (concurrent-mutation race) return 207 Multi-Status, same as transport failures — NOT 500. Archive errors are degradations, not bugs. - **State lifecycle:** Source run transitions `Succeeded/Failed/Dead → Archived` via the existing archive pipeline. On success, `operations::archive` runs FIRST; then the server appends `RunSupersededBy { new_run_id }`. Event log reads `RunArchived, RunSupersededBy`. Ordering rationale: if RunSupersededBy fails after archive, source is cleanly archived with missing provenance (repairable). If we reversed, an archive failure after a supersede-append would leave source "superseded but still Succeeded" — a misleading projection state. Projection captures `superseded_by: Some(new_run_id)` so UIs/CLI can answer "what replaced this?" without event-log replay. - **Event stream consumers:** `RunRewound` disappears from the event stream; `RunSupersededBy` appears. Any UI element, log filter, or downstream consumer that matched `"run.rewound"` will break. Per memory, this is greenfield with no deployed consumers — confirm during implementation that no docs/web consumers reference the old event name: `rg -i rewound docs/ apps/ lib/packages/` should return only documentation strings destined for update in Unit 6. -- **API surface parity:** `docs/api-reference/fabro-api.yaml` gets two additions (`POST /runs/{id}/rewind` endpoint with `RewindRequest`/`RewindResponse` schemas, and `"run.superseded_by"` event name in the SSE schema) and zero deletions — the spec does not currently reference rewound (verified: `rg -c rewound docs/api-reference/fabro-api.yaml` = 0). Regenerate the Rust client and TypeScript client per CLAUDE.md "API workflow" after spec edits. +- **API surface parity:** `docs/api-reference/fabro-api.yaml` gets several additions: three new paths (`POST /runs/{id}/rewind`, `POST /runs/{id}/fork`, `GET /runs/{id}/timeline`) with their request/response schemas (`RewindRequest`/`RewindResponse`, `ForkRequest`/`ForkResponse`, `TimelineEntryResponse`), `RunSupersededByProps` event schema, `"run.superseded_by"` in the SSE event-name enum, and a `superseded_by` field on the `RunSummary` schema. Zero deletions — the spec does not currently reference rewound (verified: `rg -c rewound docs/api-reference/fabro-api.yaml` = 0). Regenerate the Rust client and TypeScript client per CLAUDE.md "API workflow" after spec edits. - **Integration coverage:** The `recovery.rs` scenarios (post-split) are the main integration tests that cross the CLI / server / git boundary. Unit 5 covers them. -- **Unchanged invariants:** `operations::fork`, `operations::archive`, `operations::unarchive`, `operations::resume`, and the `ensure_not_archived` guards on non-rewind paths (e.g., resume) stay exactly as they are. The fork op's public signature is unchanged. +- **Unchanged invariants:** `operations::fork` (the sync git function), `operations::archive`, `operations::unarchive`, `operations::resume`, and the `ensure_not_archived` guards on non-rewind paths (e.g., resume) stay exactly as they are. The sync `operations::fork()` signature is unchanged — the new async wrapper/handler composes around it. ## Risks & Dependencies | Risk | Mitigation | |------|------------| -| Users/scripts relying on rewind preserving the source RunId break silently. | Output text explicitly states `"new run "` so the change is loud; `--json` output includes both `source_run_id` and `new_run_id` so scripts can adapt without parsing prose. User-facing docs are updated in Unit 6 so the documented contract matches new behavior. | -| Fork-succeeded-then-archive-failed leaves an extra run on the server. | Pre-check before fork eliminates the precondition-failure case. Transport-level archive failures produce `archived: false` in the response so the CLI can surface a warning while still giving the user the new RunId. Archive is idempotent — retrying the CLI command against the same source archives it cleanly on the second attempt. | +| Users/scripts relying on rewind preserving the source RunId break silently. | Output text explicitly states `new run ` so the change is loud; `--json` output includes both `source_run_id` and `new_run_id` so scripts can adapt without parsing prose. User-facing docs are updated in Unit 6 so the documented contract matches new behavior. | +| Fork-succeeded-then-archive-failed leaves an extra run on the server. | Pre-check before fork eliminates the precondition-failure case. Transport-level archive failures produce `archived: false` in the response so the CLI can surface a warning while still giving the user the new RunId. **Cleanup: the user runs `fabro archive ` manually** (archive is idempotent). **Do NOT retry `fabro rewind`** — rewind is not idempotent; each call mints a fresh RunId via fork, so a retry would orphan another run. | | Recovery scenario changes miss a subtle assertion. | Unit 5 splits into two focused scenarios and explicitly asserts new-RunId resumability and the event count delta. Run locally before merging. | | Stale `insta` snapshots silently accept changed output. | Follow CLAUDE.md discipline: `cargo insta pending-snapshots` before `cargo insta accept`; accept per-file, never globally. | | Archive precondition rejects non-terminal runs that `ensure_not_archived` used to allow. | Resolved via User Decisions: accept the narrowing. Documented in Scope Boundaries and the CLI error message; users who need to rewind a paused/blocked run cancel-or-kill it first. | | OpenAPI spec drift after adding the endpoint and event. | `fabro-server` conformance test catches router/spec divergence. Regenerate both Rust and TypeScript clients immediately after spec edits; commit the generated updates in the same commit as the spec changes. | | New `RunSupersededBy` event shape conflicts with fabro-web or external SSE consumers. | Search `apps/fabro-web` and any external consumer repos for `run\.rewound` and related event-name strings before merging. Currently greenfield, but a one-line grep keeps the assumption honest. | -| Server-side rewind/timeline endpoints reject runs whose `working_directory` isn't server-accessible (501). | Documented explicitly in error-path scenarios. CLI surfaces the 501 clearly and suggests checkout-to-local-path as the workaround. In practice, most runs today are local. Remote/sandbox runs are a future concern that may need a streaming-fork-from-client protocol. | +| Server-side rewind/fork/timeline endpoints reject runs whose `working_directory` isn't server-accessible (501). | Accepted as a hard limitation in this plan. The CLI surfaces a clear, non-misleading error (no retry guidance). In practice most runs today are local and server+CLI share a filesystem; remote/sandbox runs need a follow-up (override mechanism, rebuild endpoint, or streaming-fork-from-client protocol). Not solved here. | | `RunSupersededBy` omitted on 207 leaves source with no source-side audit trail of the rewind. | Accepted trade-off per event-ordering-invariant decision. Response body still carries `new_run_id`, so forward-direction audit (new→source) is available via the deferred `forked_from` provenance follow-up. Backward direction (source→new) is only available on archive success — which is the common case. | ## Documentation / Operational Notes -- User-facing docs teach the old in-place-rewind model explicitly and must be updated (see Unit 5): +- User-facing docs teach the old in-place-rewind model explicitly and must be updated (see Unit 6): - `docs/execution/checkpoints.mdx:140-159` — documents `fabro rewind ` followed by `fabro resume ` using the same ID; contrasts rewind (destructive, resets original) against fork (independent copy). - `docs/reference/cli.mdx` — CLI reference entry for `fabro rewind`; lines around 584 contrast rewind vs. fork as in-place-reset vs. independent-copy. - Changelog entries: `docs/changelog/2026-03-14.mdx:26-34` and `docs/changelog/2026-03-15.mdx:8` are historical and can stay, but a new changelog entry for this semantic change is required. -- **OpenAPI spec + client regeneration.** Unit 2 adds `POST /runs/{id}/rewind` with `RewindRequest`/`RewindResponse` schemas and the `"run.superseded_by"` event name to `docs/api-reference/fabro-api.yaml`. After spec edits, rerun `cargo build -p fabro-api` (progenitor regenerates Rust types + reqwest client) and `cd lib/packages/fabro-api-client && bun run generate` (openapi-generator regenerates TS client). The `fabro-server` conformance test catches spec/router drift — run it locally after the endpoint is wired. +- **OpenAPI spec + client regeneration.** Unit 2 adds three paths (`POST /runs/{id}/rewind`, `POST /runs/{id}/fork`, `GET /runs/{id}/timeline`) with their request/response schemas, plus `RunSupersededByProps` + `"run.superseded_by"` event wiring, plus the `superseded_by` field on `RunSummary`. After spec edits, rerun `cargo build -p fabro-api` (progenitor regenerates Rust types + reqwest client) and `cd lib/packages/fabro-api-client && bun run generate` (openapi-generator regenerates TS client). The `fabro-server` conformance test catches spec/router drift — run it locally after the endpoints are wired. - No rollout concerns — greenfield, no migration. ## Sources & References diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx index 90eb3a8e7..69a0cecc0 100644 --- a/docs/reference/cli.mdx +++ b/docs/reference/cli.mdx @@ -536,7 +536,7 @@ fabro graph run.toml ## `fabro rewind` -Rewind a workflow run to an earlier checkpoint. This resets both the run branch and metadata branch refs so that `fabro resume` continues from the target checkpoint. +Rewind a terminal workflow run to an earlier checkpoint. This creates a replacement run at the target checkpoint, archives the source run, and prints the new run ID for `fabro resume`. ```bash fabro rewind [TARGET] @@ -548,7 +548,7 @@ fabro rewind --list | `` | Run ID or unambiguous prefix (required) | | `[TARGET]` | Checkpoint to rewind to: node name, `node@visit`, or `@ordinal` (1-based). Omit to show the timeline. | | `--list` | Show the checkpoint timeline instead of rewinding | -| `--no-push` | Skip force-pushing rewound refs to the remote | +| `--no-push` | Skip pushing the replacement run refs to the remote | Target formats: @@ -558,17 +558,19 @@ Target formats: | `node@N` | `plan@2` | The 2nd visit of the named node | | `@N` | `@3` | The 3rd checkpoint in sequence | -After rewinding, resume from the earlier point: +After rewinding, resume the replacement run: ```bash -fabro resume +fabro resume ``` +The source run must be `succeeded`, `failed`, or `dead`. Archived sources are rejected until you unarchive them. Rewind is not idempotent; if it creates the replacement run but cannot archive the source, archive the source manually instead of retrying rewind. + See [Checkpoints](/execution/checkpoints#rewinding-to-an-earlier-checkpoint) for background on how checkpointing works. ## `fabro fork` -Fork a new run from an existing run's checkpoint. Unlike `fabro rewind`, which resets the original run in place, `fabro fork` creates an independent copy — the original run stays intact. +Fork a new run from an existing run's checkpoint. Unlike `fabro rewind`, which archives the original run after creating a replacement, `fabro fork` creates an independent copy and leaves the original run intact. ```bash fabro fork [TARGET] @@ -588,6 +590,8 @@ Target formats are the same as [`fabro rewind`](#fabro-rewind). After forking, r fabro resume ``` +`fabro rewind`, `fabro fork`, and their `--list` modes run through the server. The server must be able to access the run's recorded working directory. Timeline listing reads the metadata branch without rebuilding it; if the metadata branch is missing, the list can be empty. + See [Checkpoints — Forking a run](/execution/checkpoints#forking-a-run) for when to use fork vs. rewind. ## `fabro logs` diff --git a/lib/crates/fabro-cli/src/commands/mod.rs b/lib/crates/fabro-cli/src/commands/mod.rs index f9408d15b..1b1fa6f1b 100644 --- a/lib/crates/fabro-cli/src/commands/mod.rs +++ b/lib/crates/fabro-cli/src/commands/mod.rs @@ -11,7 +11,6 @@ pub(crate) mod parse; pub(crate) mod pr; pub(crate) mod preflight; pub(crate) mod provider; -pub(crate) mod rebuild; pub(crate) mod render_graph; pub(crate) mod repo; pub(crate) mod run; diff --git a/lib/crates/fabro-cli/src/commands/rebuild.rs b/lib/crates/fabro-cli/src/commands/rebuild.rs deleted file mode 100644 index 526fc8ff2..000000000 --- a/lib/crates/fabro-cli/src/commands/rebuild.rs +++ /dev/null @@ -1,24 +0,0 @@ -use std::sync::Arc; -use std::time::Duration; - -use anyhow::Result; -use fabro_store::{Database, EventEnvelope, EventPayload, RunDatabase}; -use object_store::memory::InMemory; - -pub(crate) async fn rebuild_run_store( - run_id: &fabro_types::RunId, - events: &[EventEnvelope], -) -> Result { - let store = Arc::new(Database::new( - Arc::new(InMemory::new()), - "", - Duration::from_millis(1), - None, - )); - let run_store = store.create_run(run_id).await?; - for event in events { - let payload = EventPayload::new(event.event.to_value()?, run_id)?; - run_store.append_event(&payload).await?; - } - Ok(run_store) -} diff --git a/lib/crates/fabro-cli/src/commands/run/fork.rs b/lib/crates/fabro-cli/src/commands/run/fork.rs index cf5dc7296..ad83d6fdf 100644 --- a/lib/crates/fabro-cli/src/commands/run/fork.rs +++ b/lib/crates/fabro-cli/src/commands/run/fork.rs @@ -1,71 +1,53 @@ -use anyhow::{Context, Result}; -use fabro_checkpoint::git::Store; +use anyhow::Result; +use fabro_api::types::ForkRequest; use fabro_util::terminal::Styles; -use fabro_workflow::operations::{ForkRunInput, RewindTarget, build_timeline_or_rebuild, fork}; -use git2::Repository; use crate::args::ForkArgs; use crate::command_context::CommandContext; -use crate::commands::rebuild::rebuild_run_store; use crate::shared::print_json_pretty; -use crate::shared::repo::ensure_matching_repo_origin; pub(crate) async fn run(args: &ForkArgs, styles: &Styles, base_ctx: &CommandContext) -> Result<()> { - let repo = Repository::discover(".").context("not in a git repository")?; let printer = base_ctx.printer(); let ctx = base_ctx.with_target(&args.server)?; let client = ctx.server().await?; let run_id = client.resolve_run(&args.run_id).await?.run_id; - let state = client.get_run_state(&run_id).await?; - let run_spec = state.spec.context("Failed to load run spec from store")?; - ensure_matching_repo_origin(run_spec.repo_origin_url.as_deref(), "fork")?; - let store = Store::new(repo); - let events = client.list_run_events(&run_id, None, None).await?; - let run_store = rebuild_run_store(&run_id, &events).await?; - - let timeline = build_timeline_or_rebuild(&store, Some(&run_store), &run_id).await?; + super::rewind::ensure_origin_if_local(client.as_ref(), &run_id, "fork").await?; if args.list { + let timeline = client.run_timeline(&run_id).await?; if ctx.json_output() { print_json_pretty(&super::rewind::timeline_entries_json(&timeline))?; return Ok(()); } - super::rewind::print_timeline(&timeline, styles, printer); + let entries = super::rewind::timeline_entries_json(&timeline); + super::rewind::print_timeline(&entries, styles, printer); return Ok(()); } - let target = args - .target - .as_deref() - .map(str::parse::) - .transpose()?; - let new_run_id = fork(&store, &ForkRunInput { - source_run_id: run_id, - target, - push: !args.no_push, - })?; - - let run_id_string = run_id.to_string(); - let new_run_id_string = new_run_id.to_string(); + let response = client + .fork_run(&run_id, ForkRequest { + target: args.target.clone(), + push: Some(!args.no_push), + }) + .await?; if ctx.json_output() { - let target = args.target.clone().unwrap_or_else(|| "latest".to_string()); print_json_pretty(&serde_json::json!({ - "source_run_id": run_id_string, - "new_run_id": new_run_id_string, - "target": target, + "source_run_id": response.source_run_id, + "new_run_id": response.new_run_id, + "target": response.target, }))?; } else { fabro_util::printerr!( printer, "\nForked run {} -> {}", - &run_id_string[..8.min(run_id_string.len())], - &new_run_id_string[..8.min(new_run_id_string.len())] + super::rewind::short_id(&response.source_run_id), + super::rewind::short_id(&response.new_run_id) ); fabro_util::printerr!( printer, "To resume: fabro resume {}", - &new_run_id_string[..8.min(new_run_id_string.len())] + super::rewind::short_id(&response.new_run_id) ); } diff --git a/lib/crates/fabro-cli/src/commands/run/rewind.rs b/lib/crates/fabro-cli/src/commands/run/rewind.rs index 1a4556026..d2ffc02b0 100644 --- a/lib/crates/fabro-cli/src/commands/run/rewind.rs +++ b/lib/crates/fabro-cli/src/commands/run/rewind.rs @@ -1,21 +1,15 @@ -use anyhow::{Context, Result}; +use anyhow::Result; use cli_table::format::{Border, Separator}; use cli_table::{Cell, CellStruct, Color, Style, Table}; -use fabro_checkpoint::git::Store; -use fabro_types::run_event::{CheckpointCompletedProps, RunRewoundProps, RunSubmittedProps}; -use fabro_types::{EventBody, RunEvent}; +use fabro_api::types::{RewindRequest, TimelineEntryResponse}; +use fabro_types::RunId; use fabro_util::printer::Printer; use fabro_util::terminal::Styles; -use fabro_workflow::git::MetadataStore; -use fabro_workflow::operations::{ - RewindInput, RewindTarget, RunTimeline, TimelineEntry, build_timeline_or_rebuild, rewind, -}; use git2::Repository; use serde::Serialize; use crate::args::RewindArgs; use crate::command_context::CommandContext; -use crate::commands::rebuild::rebuild_run_store; use crate::server_client::Client; use crate::shared::repo::ensure_matching_repo_origin; use crate::shared::{color_if, print_json_pretty}; @@ -33,191 +27,104 @@ pub(crate) async fn run( styles: &Styles, base_ctx: &CommandContext, ) -> Result<()> { - let repo = Repository::discover(".").context("not in a git repository")?; let printer = base_ctx.printer(); let ctx = base_ctx.with_target(&args.server)?; let client = ctx.server().await?; let run_id = client.resolve_run(&args.run_id).await?.run_id; - let state = client.get_run_state(&run_id).await?; - let current_status = state - .status - .context("run has no recorded status — cannot rewind")?; - let run_spec = state.spec.context("Failed to load run spec from store")?; - ensure_matching_repo_origin(run_spec.repo_origin_url.as_deref(), "rewind")?; - let store = Store::new(repo); - let events = client.list_run_events(&run_id, None, None).await?; - let run_store = rebuild_run_store(&run_id, &events).await?; - - let timeline = build_timeline_or_rebuild(&store, Some(&run_store), &run_id).await?; + ensure_origin_if_local(client.as_ref(), &run_id, "rewind").await?; if args.list || args.target.is_none() { + let timeline = client.run_timeline(&run_id).await?; if ctx.json_output() { print_json_pretty(&timeline_entries_json(&timeline))?; return Ok(()); } - print_timeline(&timeline, styles, printer); + print_timeline(&timeline_entries_json(&timeline), styles, printer); return Ok(()); } - let target_arg = args + let target = args .target - .as_deref() + .clone() .expect("rewind target should be present unless listing"); - let target = target_arg.parse::()?; - - rewind(&store, &RewindInput { - run_id, - target: target.clone(), - push: !args.no_push, - current_status, - })?; - let entry = timeline.resolve(&target)?; - reset_rewound_run_state(client.as_ref(), &store, &run_id, entry).await?; - - let run_id_string = run_id.to_string(); + let result = client + .rewind_run(&run_id, RewindRequest { + target: Some(target), + push: Some(!args.no_push), + }) + .await?; + let response = result.response; if ctx.json_output() { print_json_pretty(&serde_json::json!({ - "run_id": run_id_string, - "target": target_arg, + "source_run_id": response.source_run_id, + "new_run_id": response.new_run_id, + "target": response.target, + "archived": response.archived, + "archive_error": response.archive_error, + "status": result.status, }))?; } else { fabro_util::printerr!( printer, - "\nTo resume: fabro resume {}", - &run_id_string[..8.min(run_id_string.len())] + "\nRewound {}; new run {}", + short_id(&response.source_run_id), + short_id(&response.new_run_id) ); + fabro_util::printerr!( + printer, + "To resume: fabro resume {}", + short_id(&response.new_run_id) + ); + if !response.archived { + let archive_error = response.archive_error.as_deref().unwrap_or("unknown error"); + fabro_util::printerr!( + printer, + "Warning: source not archived: {archive_error}. Run `fabro archive {}` to finish.", + short_id(&response.source_run_id) + ); + } } Ok(()) } -pub(crate) fn timeline_entries_json(timeline: &RunTimeline) -> Vec { - timeline - .entries +pub(crate) async fn ensure_origin_if_local( + client: &Client, + run_id: &RunId, + verb: &str, +) -> Result<()> { + if Repository::discover(".").is_err() { + return Ok(()); + } + + let state = client.get_run_state(run_id).await?; + if let Some(run_spec) = state.spec { + ensure_matching_repo_origin(run_spec.repo_origin_url.as_deref(), verb)?; + } + Ok(()) +} + +pub(crate) fn timeline_entries_json(entries: &[TimelineEntryResponse]) -> Vec { + entries .iter() .map(|entry| TimelineEntryJson { - ordinal: entry.ordinal, + ordinal: usize::try_from(entry.ordinal.get()) + .expect("timeline ordinal should fit in usize"), node_name: entry.node_name.clone(), - visit: entry.visit, + visit: usize::try_from(entry.visit.get()) + .expect("timeline visit should fit in usize"), run_commit_sha: entry.run_commit_sha.clone(), }) .collect() } -async fn reset_rewound_run_state( - client: &Client, - git_store: &Store, - run_id: &fabro_types::RunId, - entry: &TimelineEntry, -) -> Result<()> { - let state = client.get_run_state(run_id).await.map_err(|err| { - anyhow::anyhow!("failed to load durable store state before rewind: {err}") - })?; - - let definition_blob = state.spec.as_ref().and_then(|run| run.definition_blob); - if state.spec.is_none() { - anyhow::bail!("failed to restore run spec after rewind: missing run metadata"); - } - let checkpoint = MetadataStore::read_run_projection(git_store.repo_dir(), &run_id.to_string())? - .and_then(|projection| projection.checkpoint) - .context("rewound metadata branch is missing run.json checkpoint state")?; - let previous_status = state.status.map(|status| status.to_string()); - - client - .append_run_event( - run_id, - &run_event( - *run_id, - None, - EventBody::RunRewound(RunRewoundProps { - target_checkpoint_ordinal: entry.ordinal, - target_node_id: entry.node_name.clone(), - target_visit: entry.visit, - previous_status, - run_commit_sha: entry.run_commit_sha.clone(), - }), - ), - ) - .await - .map_err(|err| anyhow::anyhow!("failed to append run rewound event: {err}"))?; - client - .append_run_event(run_id, &restored_checkpoint_event(*run_id, &checkpoint)) - .await - .map_err(|err| anyhow::anyhow!("failed to append restored checkpoint event: {err}"))?; - client - .append_run_event( - run_id, - &run_event( - *run_id, - None, - EventBody::RunSubmitted(RunSubmittedProps { definition_blob }), - ), - ) - .await - .map_err(|err| anyhow::anyhow!("failed to append restored run status event: {err}"))?; - Ok(()) +pub(crate) fn short_id(run_id: &str) -> &str { + &run_id[..8.min(run_id.len())] } -fn restored_checkpoint_event( - run_id: fabro_types::RunId, - checkpoint: &fabro_types::Checkpoint, -) -> RunEvent { - let current_status = checkpoint - .node_outcomes - .get(&checkpoint.current_node) - .map_or_else( - || "success".to_string(), - |outcome| outcome.status.to_string(), - ); - run_event( - run_id, - Some(checkpoint.current_node.clone()), - EventBody::CheckpointCompleted(CheckpointCompletedProps { - status: current_status, - current_node: checkpoint.current_node.clone(), - completed_nodes: checkpoint.completed_nodes.clone(), - node_retries: checkpoint.node_retries.clone().into_iter().collect(), - context_values: checkpoint.context_values.clone().into_iter().collect(), - node_outcomes: checkpoint.node_outcomes.clone().into_iter().collect(), - next_node_id: checkpoint.next_node_id.clone(), - git_commit_sha: checkpoint.git_commit_sha.clone(), - loop_failure_signatures: checkpoint - .loop_failure_signatures - .iter() - .map(|(sig, count)| (sig.to_string(), *count)) - .collect(), - restart_failure_signatures: checkpoint - .restart_failure_signatures - .iter() - .map(|(sig, count)| (sig.to_string(), *count)) - .collect(), - node_visits: checkpoint.node_visits.clone().into_iter().collect(), - diff: None, - }), - ) -} - -fn run_event(run_id: fabro_types::RunId, node_id: Option, body: EventBody) -> RunEvent { - RunEvent { - id: ulid::Ulid::new().to_string(), - ts: chrono::Utc::now(), - run_id, - node_id, - node_label: None, - stage_id: None, - parallel_group_id: None, - parallel_branch_id: None, - session_id: None, - parent_session_id: None, - tool_call_id: None, - actor: None, - body, - } -} - -pub(crate) fn print_timeline(timeline: &RunTimeline, styles: &Styles, printer: Printer) { - if timeline.entries.is_empty() { +pub(crate) fn print_timeline(entries: &[TimelineEntryJson], styles: &Styles, printer: Printer) { + if entries.is_empty() { fabro_util::printerr!(printer, "No checkpoints found."); return; } @@ -229,8 +136,7 @@ pub(crate) fn print_timeline(timeline: &RunTimeline, styles: &Styles, printer: P "Details".cell().bold(use_color), ]; - let rows: Vec> = timeline - .entries + let rows: Vec> = entries .iter() .map(|entry| { let ordinal_str = format!("@{}", entry.ordinal); @@ -238,9 +144,6 @@ pub(crate) fn print_timeline(timeline: &RunTimeline, styles: &Styles, printer: P if entry.visit > 1 { details.push(format!("visit {}, loop", entry.visit)); } - if timeline.parallel_map.contains_key(&entry.node_name) { - details.push("parallel interior".to_string()); - } if entry.run_commit_sha.is_none() { details.push("no run commit".to_string()); } diff --git a/lib/crates/fabro-cli/tests/it/cmd/fork.rs b/lib/crates/fabro-cli/tests/it/cmd/fork.rs index 03ab5e9aa..e04b5dfa7 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/fork.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/fork.rs @@ -48,8 +48,7 @@ fn fork_outside_git_repo_errors() { exit_code: 1 ----- stdout ----- ----- stderr ----- - error: not in a git repository - > could not find repository at '.'; class=Repository (6); code=NotFound (-3) + error: No run found matching '[ULID]' (tried run ID prefix and workflow name) "); } diff --git a/lib/crates/fabro-cli/tests/it/cmd/resume.rs b/lib/crates/fabro-cli/tests/it/cmd/resume.rs index bff92d58d..d5e0f9df7 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/resume.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/resume.rs @@ -62,27 +62,16 @@ fn resume_rewound_run_succeeds() { let context = test_context!(); let setup = setup_git_backed_changed_run(&context); - let rewind = context - .command() - .current_dir(&setup.repo_dir) - .args(["rewind", &setup.run.run_id, "@1", "--no-push"]) - .output() - .expect("rewind should execute"); - assert!( - rewind.status.success(), - "rewind should succeed\nstdout:\n{}\nstderr:\n{}", - String::from_utf8_lossy(&rewind.stdout), - output_stderr(&rewind) - ); + let new_run_id = rewind_replacement_run_id(&context, &setup); let rewound_head = git_stdout(&setup.repo_dir, &[ "rev-parse", - &format!("fabro/run/{}", setup.run.run_id), + &format!("fabro/run/{new_run_id}"), ]); let mut resume_cmd = context.command(); resume_cmd.current_dir(&setup.repo_dir); resume_cmd.env("OPENAI_API_KEY", "test"); - resume_cmd.args(["resume", &setup.run.run_id]); + resume_cmd.args(["resume", &new_run_id]); let resume_output = resume_cmd.output().expect("resume should execute"); assert!( resume_output.status.success(), @@ -91,23 +80,11 @@ fn resume_rewound_run_succeeds() { output_stderr(&resume_output) ); - let mut wait_filters = context.filters(); - wait_filters.push(( - r"\b\d+(\.\d+)?(ms|s)\b".to_string(), - "[DURATION]".to_string(), - )); - let mut wait_cmd = context.command(); - wait_cmd.args(["wait", &setup.run.run_id]); - fabro_snapshot!(wait_filters, wait_cmd, @" - success: true - exit_code: 0 - ----- stdout ----- - ----- stderr ----- - Succeeded [ULID] [DURATION] - "); - assert_eq!( - std::fs::read_to_string(setup.run.run_dir.join("worktree/story.txt")).unwrap(), + git_stdout(&setup.repo_dir, &[ + "show", + &format!("fabro/run/{new_run_id}:story.txt") + ]), "line 1\nline 2\nline 3\n" ); assert_eq!( @@ -116,7 +93,7 @@ fn resume_rewound_run_succeeds() { ); let resumed_head = git_stdout(&setup.repo_dir, &[ "rev-parse", - &format!("fabro/run/{}", setup.run.run_id), + &format!("fabro/run/{new_run_id}"), ]); assert_ne!(resumed_head.trim(), rewound_head.trim()); } @@ -126,17 +103,12 @@ fn resume_detached_does_not_create_launcher_record() { let context = test_context!(); let setup = setup_git_backed_changed_run(&context); - context - .command() - .current_dir(&setup.repo_dir) - .args(["rewind", &setup.run.run_id, "@1", "--no-push"]) - .assert() - .success(); + let new_run_id = rewind_replacement_run_id(&context, &setup); let mut resume_cmd = context.command(); resume_cmd.current_dir(&setup.repo_dir); resume_cmd.env("OPENAI_API_KEY", "test"); - resume_cmd.args(["resume", "--detach", &setup.run.run_id]); + resume_cmd.args(["resume", "--detach", &new_run_id]); let resume_output = resume_cmd.output().expect("resume should execute"); assert!( resume_output.status.success(), @@ -149,15 +121,45 @@ fn resume_detached_does_not_create_launcher_record() { !context .storage_dir .join("launchers") - .join(format!("{}.json", setup.run.run_id)) + .join(format!("{new_run_id}.json")) .exists(), "server-owned resume should not create a launcher record" ); context .command() - .args(["wait", &setup.run.run_id]) + .args(["wait", &new_run_id]) .timeout(SHARED_DAEMON_TIMEOUT) .assert() .success(); } + +fn rewind_replacement_run_id( + context: &fabro_test::TestContext, + setup: &super::support::GitRunSetup, +) -> String { + let rewind = context + .command() + .current_dir(&setup.repo_dir) + .args(["rewind", &setup.run.run_id, "@1", "--no-push", "--json"]) + .output() + .expect("rewind should execute"); + assert!( + rewind.status.success(), + "rewind should succeed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&rewind.stdout), + output_stderr(&rewind) + ); + + let response: serde_json::Value = + serde_json::from_slice(&rewind.stdout).expect("rewind json should parse"); + assert_eq!( + response["source_run_id"].as_str(), + Some(setup.run.run_id.as_str()) + ); + assert_eq!(response["archived"].as_bool(), Some(true)); + response["new_run_id"] + .as_str() + .expect("rewind response should include new_run_id") + .to_string() +} diff --git a/lib/crates/fabro-cli/tests/it/cmd/rewind.rs b/lib/crates/fabro-cli/tests/it/cmd/rewind.rs index 7632de684..94efdbcb9 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/rewind.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/rewind.rs @@ -2,8 +2,8 @@ use fabro_test::{fabro_snapshot, run_and_format, test_context}; use insta::assert_snapshot; use super::support::{ - git_filters, git_stdout, output_stderr as support_stderr, run_branch_commits_since_base, - run_events, run_state, setup_git_backed_changed_run, + git_filters, git_stdout, metadata_run_ids, output_stderr as support_stderr, + run_branch_commits_since_base, run_events, run_state, setup_git_backed_changed_run, }; #[test] @@ -48,8 +48,7 @@ fn rewind_outside_git_repo_errors() { exit_code: 1 ----- stdout ----- ----- stderr ----- - error: not in a git repository - > could not find repository at '.'; class=Repository (6); code=NotFound (-3) + error: No run found matching '[ULID]' (tried run ID prefix and workflow name) "); } @@ -76,6 +75,7 @@ fn rewind_list_prints_timeline_for_completed_git_run() { fn rewind_target_updates_metadata_and_resume_hint() { let context = test_context!(); let setup = setup_git_backed_changed_run(&context); + let before = metadata_run_ids(&setup.repo_dir); let expected_run_head = run_branch_commits_since_base(&setup.repo_dir, &setup.run.run_id, &setup.base_sha) .into_iter() @@ -92,37 +92,40 @@ fn rewind_target_updates_metadata_and_resume_hint() { exit_code: 0 ----- stdout ----- ----- stderr ----- - Rewound metadata branch to @1 (step_one) - Rewound run branch fabro/run/[ULID] to [SHA] + Rewound [RUN_PREFIX]; new run [RUN_PREFIX] To resume: fabro resume [RUN_PREFIX] "); assert!(output.status.success(), "rewind should succeed"); + let after = metadata_run_ids(&setup.repo_dir); + let new_run_ids: Vec<_> = after.difference(&before).cloned().collect(); + assert_eq!( + new_run_ids.len(), + 1, + "rewind should create one replacement run" + ); + let new_run_id = &new_run_ids[0]; + let run_head = git_stdout(&setup.repo_dir, &[ "rev-parse", - &format!("fabro/run/{}", setup.run.run_id), + &format!("fabro/run/{new_run_id}"), ]); assert_eq!(run_head.trim(), expected_run_head); - let mut list_cmd = context.command(); - list_cmd.current_dir(&setup.repo_dir); - list_cmd.args(["rewind", &setup.run.run_id, "--list"]); - let list_output = list_cmd.output().expect("rewind --list should execute"); - assert!(list_output.status.success(), "rewind --list should succeed"); - let list = support_stderr(&list_output); - assert!( - list.contains("@1"), - "rewound timeline should keep @1: {list}" - ); - assert!( - !list.contains("@2"), - "rewound timeline should drop @2: {list}" + let state = run_state(&setup.run.run_dir); + assert!(matches!( + state.status, + Some(fabro_types::RunStatus::Archived { .. }) + )); + assert_eq!( + state.superseded_by.map(|run_id| run_id.to_string()), + Some(new_run_id.clone()) ); } #[test] -fn rewind_preserves_event_history_and_clears_terminal_snapshot_state() { +fn rewind_archives_source_and_records_superseded_by() { let context = test_context!(); let setup = setup_git_backed_changed_run(&context); let before_events = run_events(&setup.run.run_dir); @@ -147,8 +150,8 @@ fn rewind_preserves_event_history_and_clears_terminal_snapshot_state() { let after_events = run_events(&setup.run.run_dir); assert_eq!( after_events.len(), - before_events.len() + 3, - "rewind should append run.rewound, checkpoint.completed, and run.submitted" + before_events.len() + 2, + "rewind should append run.archived and run.superseded_by" ); assert_eq!( after_events[..before_events.len()] @@ -163,38 +166,17 @@ fn rewind_preserves_event_history_and_clears_terminal_snapshot_state() { ); assert_eq!( after_events[before_events.len()].event.event_name(), - "run.rewound" + "run.archived" ); assert_eq!( after_events[before_events.len() + 1].event.event_name(), - "checkpoint.completed" - ); - assert_eq!( - after_events[before_events.len() + 2].event.event_name(), - "run.submitted" - ); - assert!( - after_events[before_events.len() + 2] - .event - .properties() - .unwrap()["definition_blob"] - .is_string(), - "rewind should re-emit run.submitted with the definition_blob" + "run.superseded_by" ); let state = run_state(&setup.run.run_dir); - assert_eq!(state.status, Some(fabro_types::RunStatus::Submitted)); - assert!(state.conclusion.is_none(), "rewind should clear conclusion"); - assert!( - state.final_patch.is_none(), - "rewind should clear final patch" - ); - assert!( - state.pull_request.is_none(), - "rewind should clear pull request" - ); - assert!( - state.is_empty(), - "rewind should clear node state that belonged to the prior execution" - ); + assert!(matches!( + state.status, + Some(fabro_types::RunStatus::Archived { .. }) + )); + assert!(state.superseded_by.is_some()); } diff --git a/lib/crates/fabro-cli/tests/it/cmd/support.rs b/lib/crates/fabro-cli/tests/it/cmd/support.rs index fa1a5d48e..3698c4775 100644 --- a/lib/crates/fabro-cli/tests/it/cmd/support.rs +++ b/lib/crates/fabro-cli/tests/it/cmd/support.rs @@ -578,6 +578,14 @@ pub(crate) fn git_filters(context: &TestContext) -> Vec<(String, String)> { r"(-> )[0-9A-HJKMNP-TV-Z]{8}\b".to_string(), "$1[RUN_PREFIX]".to_string(), )); + filters.push(( + r"(Rewound )[0-9A-HJKMNP-TV-Z]{8}\b".to_string(), + "$1[RUN_PREFIX]".to_string(), + )); + filters.push(( + r"(; new run )[0-9A-HJKMNP-TV-Z]{8}\b".to_string(), + "$1[RUN_PREFIX]".to_string(), + )); filters } diff --git a/lib/crates/fabro-cli/tests/it/scenario/recovery.rs b/lib/crates/fabro-cli/tests/it/scenario/recovery.rs index cc6716ed3..7cfa42790 100644 --- a/lib/crates/fabro-cli/tests/it/scenario/recovery.rs +++ b/lib/crates/fabro-cli/tests/it/scenario/recovery.rs @@ -6,13 +6,12 @@ use std::collections::BTreeSet; use std::path::Path; -use fabro_checkpoint::branch::BranchStore; use fabro_checkpoint::git::Store as GitStore; use fabro_store::RunProjection; use fabro_test::{fabro_snapshot, test_context}; use fabro_types::Checkpoint; use fabro_workflow::operations::{RunTimeline, build_timeline}; -use git2::{Repository, Signature}; +use git2::Repository; use crate::support::unique_run_id; @@ -29,32 +28,6 @@ fn list_metadata_run_ids(repo_dir: &Path) -> BTreeSet { .collect() } -fn metadata_checkpoints(repo_dir: &Path, run_id: &str) -> Vec { - let repo = Repository::discover(repo_dir).expect("recovery fixture should be a git repo"); - let store = GitStore::new(repo); - let sig = - Signature::now("Fabro", "noreply@fabro.sh").expect("test recovery signature should build"); - let branch = format!("fabro/meta/{run_id}"); - let bs = BranchStore::new(&store, &branch, &sig); - - bs.log(100) - .expect("metadata branch log should load") - .iter() - .rev() - .filter(|commit| commit.message.starts_with("checkpoint")) - .map(|commit| { - let projection_blob = store - .read_blob_at(commit.oid, "run.json") - .expect("projection blob should load") - .expect("projection blob should exist"); - serde_json::from_slice::(&projection_blob) - .expect("projection blob should deserialize") - .checkpoint - .expect("projection checkpoint should exist") - }) - .collect() -} - fn latest_metadata_checkpoint(repo_dir: &Path, run_id: &str) -> Checkpoint { let repo = Repository::discover(repo_dir).expect("recovery fixture should be a git repo"); let store = GitStore::new(repo); @@ -80,14 +53,6 @@ fn timeline_run_shas(repo_dir: &Path, run_id: &str) -> Vec> { .collect() } -fn timeline_node_names(repo_dir: &Path, run_id: &str) -> Vec { - build_timeline_when_ready(repo_dir, run_id) - .entries - .into_iter() - .map(|entry| entry.node_name) - .collect() -} - #[expect( clippy::disallowed_methods, reason = "This sync git integration helper polls until metadata commits become readable without requiring Tokio." @@ -182,7 +147,7 @@ digraph Recovery { } #[test] -fn rewind_and_fork_recover_missing_metadata_from_real_run_state() { +fn rewind_list_reports_empty_timeline_when_metadata_branch_is_missing() { let context = test_context!(); context.ensure_home_server_auth_methods(); let repo_dir = tempfile::tempdir().unwrap(); @@ -206,10 +171,6 @@ fn rewind_and_fork_recover_missing_metadata_from_real_run_state() { .assert() .success(); - let mut filters = Vec::new(); - filters.push((r"\b[0-9a-f]{7,40}\b".to_string(), "[SHA]".to_string())); - filters.extend(context.filters()); - delete_metadata_branch_when_ready(repo_dir.path(), &source_run_id); assert!( @@ -221,23 +182,44 @@ fn rewind_and_fork_recover_missing_metadata_from_real_run_state() { rewind_list.current_dir(repo_dir.path()); rewind_list.args(["rewind", &source_run_id, "--list"]); rewind_list.timeout(std::time::Duration::from_secs(15)); - rewind_list.assert().success(); + fabro_snapshot!(context.filters(), rewind_list, @" + success: true + exit_code: 0 + ----- stdout ----- + ----- stderr ----- + No checkpoints found. + "); - let rebuilt_nodes = timeline_node_names(repo_dir.path(), &source_run_id); - assert_eq!(rebuilt_nodes.last().map(String::as_str), Some("build")); assert!( - rebuilt_nodes.ends_with(&["plan".to_string(), "build".to_string()]), - "expected rebuilt timeline to end with plan -> build, got {rebuilt_nodes:?}" + list_metadata_run_ids(repo_dir.path()).is_empty(), + "server timeline should not rebuild missing metadata" ); +} - let rebuilt_checkpoints = metadata_checkpoints(repo_dir.path(), &source_run_id); - assert_eq!( - rebuilt_checkpoints - .first() - .and_then(|c| c.git_commit_sha.clone()), - None - ); - assert!(rebuilt_checkpoints.len() >= 2); +#[test] +fn fork_chain_preserves_checkpoint_metadata() { + let context = test_context!(); + context.ensure_home_server_auth_methods(); + let repo_dir = tempfile::tempdir().unwrap(); + let source_run_id = unique_run_id(); + + init_repo_with_workflow(repo_dir.path()); + + context + .command() + .current_dir(repo_dir.path()) + .args([ + "run", + "--dry-run", + "--no-retro", + "--sandbox", + "local", + "--run-id", + source_run_id.as_str(), + "workflow.fabro", + ]) + .assert() + .success(); let timeline_shas = timeline_run_shas(repo_dir.path(), &source_run_id); let build_sha = timeline_shas.last().cloned().flatten(); @@ -259,36 +241,11 @@ fn rewind_and_fork_recover_missing_metadata_from_real_run_state() { let child_checkpoint = latest_metadata_checkpoint(repo_dir.path(), child_run_id); assert_eq!(child_checkpoint.git_commit_sha, build_sha); - let mut rewind_filters = filters.clone(); - rewind_filters.push(( - regex::escape(&source_run_id[..8]), - "[RUN_PREFIX]".to_string(), - )); - rewind_filters.push((r"@\d+".to_string(), "@[ORDINAL]".to_string())); - - let mut source_rewind = context.command(); - source_rewind.current_dir(repo_dir.path()); - source_rewind.args(["rewind", &source_run_id, "build", "--no-push"]); - source_rewind.timeout(std::time::Duration::from_secs(15)); - fabro_snapshot!(rewind_filters, source_rewind, @" - success: true - exit_code: 0 - ----- stdout ----- - ----- stderr ----- - Rewound metadata branch to @[ORDINAL] (build) - Rewound run branch fabro/run/[ULID] to [SHA] - - To resume: fabro resume [RUN_PREFIX] - "); - - let rewound_timeline_shas = timeline_run_shas(repo_dir.path(), &source_run_id); - assert_eq!(rewound_timeline_shas.last().cloned().flatten(), build_sha); - let before_grandchild = list_metadata_run_ids(repo_dir.path()); context .command() .current_dir(repo_dir.path()) - .args(["fork", &source_run_id, "--no-push"]) + .args(["fork", child_run_id, "--no-push"]) .timeout(std::time::Duration::from_secs(15)) .assert() .success(); diff --git a/lib/crates/fabro-client/src/client.rs b/lib/crates/fabro-client/src/client.rs index b38a8e176..339894211 100644 --- a/lib/crates/fabro-client/src/client.rs +++ b/lib/crates/fabro-client/src/client.rs @@ -39,6 +39,11 @@ pub struct RunEventStream { buffered_events: VecDeque, } +pub struct RewindRunResult { + pub status: u16, + pub response: types::RewindResponse, +} + #[derive(Clone)] struct ClientState { client: fabro_api::ApiClient, @@ -739,6 +744,59 @@ impl Client { Ok(()) } + pub async fn rewind_run( + &self, + run_id: &RunId, + request: types::RewindRequest, + ) -> Result { + let response = self + .send_api(|client| async move { + client + .rewind_run() + .id(run_id.to_string()) + .body(request) + .send() + .await + }) + .await?; + let status = response.status().as_u16(); + Ok(RewindRunResult { + status, + response: response.into_inner(), + }) + } + + pub async fn fork_run( + &self, + run_id: &RunId, + request: types::ForkRequest, + ) -> Result { + let response = self + .send_api(|client| async move { + client + .fork_run() + .id(run_id.to_string()) + .body(request) + .send() + .await + }) + .await?; + Ok(response.into_inner()) + } + + pub async fn run_timeline(&self, run_id: &RunId) -> Result> { + let response = self + .send_api(|client| async move { + client + .get_run_timeline() + .id(run_id.to_string()) + .send() + .await + }) + .await?; + Ok(response.into_inner()) + } + pub async fn list_store_runs(&self) -> Result> { let mut all_runs = Vec::new(); let mut offset = 0_u64; diff --git a/lib/crates/fabro-server/src/demo/mod.rs b/lib/crates/fabro-server/src/demo/mod.rs index ff487dd96..b6797ab00 100644 --- a/lib/crates/fabro-server/src/demo/mod.rs +++ b/lib/crates/fabro-server/src/demo/mod.rs @@ -800,6 +800,7 @@ mod runs { start_time: Some(ts(created_at)), status: parse_run_status(status, status_reason) .unwrap_or_else(|| panic!("invalid demo run status: {status}")), + superseded_by: None, title: truncate_goal(goal), total_usd_micros, workflow_name: Some(workflow_name.into()), diff --git a/lib/crates/fabro-server/src/server.rs b/lib/crates/fabro-server/src/server.rs index be03d6d10..8a869a9bb 100644 --- a/lib/crates/fabro-server/src/server.rs +++ b/lib/crates/fabro-server/src/server.rs @@ -28,15 +28,15 @@ pub use fabro_api::types::{ CompletionMessageRole, CompletionResponse, CompletionToolChoiceMode, CompletionUsage, CreateCompletionRequest, CreateRunPullRequestRequest, CreateSecretRequest, DeleteSecretRequest, DiskUsageResponse, DiskUsageRunRow, DiskUsageSummaryRow, EventEnvelope as ApiEventEnvelope, - MergeRunPullRequestRequest, MergeRunPullRequestResponse, ModelReference, PaginatedEventList, - PaginatedRunList, PaginationMeta, PreflightResponse, PreviewUrlRequest, PreviewUrlResponse, - PruneRunEntry, PruneRunsRequest, PruneRunsResponse, QuestionType as ApiQuestionType, - RenderWorkflowGraphDirection, RenderWorkflowGraphRequest, RunArtifactEntry, - RunArtifactListResponse, RunBilling, RunBillingStage, RunBillingTotals, RunError, RunManifest, - RunStage, RunStatusResponse, SandboxFileEntry, SandboxFileListResponse, - SecretType as ApiSecretType, SshAccessRequest, SshAccessResponse, - StageStatus as ApiStageStatus, StartRunRequest, SubmitAnswerRequest, SystemFeatures, - SystemInfoResponse, SystemRunCounts, WriteBlobResponse, + ForkRequest, ForkResponse, MergeRunPullRequestRequest, MergeRunPullRequestResponse, + ModelReference, PaginatedEventList, PaginatedRunList, PaginationMeta, PreflightResponse, + PreviewUrlRequest, PreviewUrlResponse, PruneRunEntry, PruneRunsRequest, PruneRunsResponse, + QuestionType as ApiQuestionType, RenderWorkflowGraphDirection, RenderWorkflowGraphRequest, + RewindRequest, RewindResponse, RunArtifactEntry, RunArtifactListResponse, RunBilling, + RunBillingStage, RunBillingTotals, RunError, RunManifest, RunStage, RunStatusResponse, + SandboxFileEntry, SandboxFileListResponse, SecretType as ApiSecretType, SshAccessRequest, + SshAccessResponse, StageStatus as ApiStageStatus, StartRunRequest, SubmitAnswerRequest, + SystemFeatures, SystemInfoResponse, SystemRunCounts, TimelineEntryResponse, WriteBlobResponse, }; use fabro_auth::{ CredentialSource, VaultCredentialSource, auth_issue_message, parse_credential_secret, @@ -1199,6 +1199,9 @@ fn real_routes() -> Router> { .route("/runs/{id}/pause", post(pause_run)) .route("/runs/{id}/unpause", post(unpause_run)) .route("/runs/{id}/archive", post(archive_run)) + .route("/runs/{id}/rewind", post(rewind_run)) + .route("/runs/{id}/fork", post(fork_run)) + .route("/runs/{id}/timeline", get(run_timeline)) .route("/runs/{id}/unarchive", post(unarchive_run)) .route("/runs/{id}/graph", get(get_graph)) .route("/runs/{id}/stages", get(list_run_stages)) @@ -2886,6 +2889,7 @@ fn summary_to_api_run_summary(summary: fabro_types::RunSummary) -> serde_json::V "duration_ms": summary.duration_ms, "elapsed_secs": elapsed_secs(summary.duration_ms), "total_usd_micros": summary.total_usd_micros, + "superseded_by": summary.superseded_by.map(|run_id| run_id.to_string()), "created_at": created_at, }) } @@ -3426,7 +3430,7 @@ fn reconcile_live_interview_state_for_event(run: &mut ManagedRun, event: &RunEve EventBody::InterviewInterrupted(props) => { run.accepted_questions.remove(&props.question_id); } - EventBody::RunCompleted(_) | EventBody::RunFailed(_) | EventBody::RunRewound(_) => { + EventBody::RunCompleted(_) | EventBody::RunFailed(_) => { run.accepted_questions.clear(); } _ => {} @@ -6662,8 +6666,7 @@ fn actor_from_subject(subject: &AuthenticatedSubject) -> Option { /// These endpoints enforce authorization and status-transition preconditions /// (e.g. "archive only from terminal") that a direct event append would /// bypass. Other run-lifecycle events flow through this endpoint legitimately: -/// the worker subprocess emits state transitions during execution, and the -/// rewind CLI relays `RunRewound` / `RunSubmitted` here. +/// the worker subprocess emits state transitions during execution. fn denied_lifecycle_event_name(body: &EventBody) -> Option<&'static str> { match body { EventBody::RunArchived(_) => Some("run.archived"), @@ -7084,6 +7087,166 @@ async fn unarchive_run( run_archive_action(state, subject, id, ArchiveAction::Unarchive).await } +async fn rewind_run( + subject: AuthenticatedSubject, + State(state): State>, + Path(id): Path, + body: Option>, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + if let Some(response) = reject_if_archived(state.as_ref(), &id).await { + return response; + } + let request = body.map(|Json(body)| body).unwrap_or_default(); + let target = match parse_fork_target(request.target) { + Ok(target) => target, + Err(err) => return err.into_response(), + }; + let input = operations::RewindInput { + run_id: id, + target, + push: request.push.unwrap_or(true), + }; + match Box::pin(operations::rewind( + &state.store, + &input, + actor_from_subject(&subject), + )) + .await + { + Ok(operations::RewindOutcome::Full { + source_run_id, + new_run_id, + target, + archived, + }) => ( + StatusCode::OK, + Json(RewindResponse { + source_run_id: source_run_id.to_string(), + new_run_id: new_run_id.to_string(), + target: target.response_target(), + archived, + archive_error: None, + }), + ) + .into_response(), + Ok(operations::RewindOutcome::Partial { + source_run_id, + new_run_id, + target, + archive_error, + }) => ( + StatusCode::MULTI_STATUS, + Json(RewindResponse { + source_run_id: source_run_id.to_string(), + new_run_id: new_run_id.to_string(), + target: target.response_target(), + archived: false, + archive_error: Some(archive_error), + }), + ) + .into_response(), + Err(err) => workflow_operation_error_response(err), + } +} + +async fn fork_run( + _subject: AuthenticatedSubject, + State(state): State>, + Path(id): Path, + body: Option>, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + if let Some(response) = reject_if_archived(state.as_ref(), &id).await { + return response; + } + let request = body.map(|Json(body)| body).unwrap_or_default(); + let target = match parse_fork_target(request.target) { + Ok(target) => target, + Err(err) => return err.into_response(), + }; + let input = operations::ForkRunInput { + source_run_id: id, + target, + push: request.push.unwrap_or(true), + }; + match operations::fork_run(&state.store, &input).await { + Ok(outcome) => ( + StatusCode::OK, + Json(ForkResponse { + source_run_id: outcome.source_run_id.to_string(), + new_run_id: outcome.new_run_id.to_string(), + target: outcome.target.response_target(), + }), + ) + .into_response(), + Err(err) => workflow_operation_error_response(err), + } +} + +async fn run_timeline( + _auth: AuthenticatedService, + State(state): State>, + Path(id): Path, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + match operations::timeline(&state.store, &id).await { + Ok(entries) => Json( + entries + .into_iter() + .map(|entry| TimelineEntryResponse { + ordinal: std::num::NonZeroU64::new(entry.ordinal as u64) + .expect("timeline ordinals start at 1"), + node_name: entry.node_name, + visit: std::num::NonZeroU64::new(entry.visit as u64) + .expect("timeline visits start at 1"), + run_commit_sha: entry.run_commit_sha, + }) + .collect::>(), + ) + .into_response(), + Err(err) => workflow_operation_error_response(err), + } +} + +fn parse_fork_target(target: Option) -> Result, ApiError> { + target + .map(|target| { + target + .parse::() + .map_err(|err| ApiError::bad_request(err.to_string())) + }) + .transpose() +} + +fn workflow_operation_error_response(err: WorkflowError) -> Response { + match err { + WorkflowError::Parse(message) | WorkflowError::Validation(message) => { + ApiError::bad_request(message).into_response() + } + WorkflowError::ValidationFailed { .. } => { + ApiError::bad_request("Validation failed").into_response() + } + WorkflowError::Precondition(message) => { + ApiError::new(StatusCode::CONFLICT, message).into_response() + } + WorkflowError::RunNotFound(_) => ApiError::not_found("Run not found.").into_response(), + WorkflowError::Unsupported(message) => { + ApiError::new(StatusCode::NOT_IMPLEMENTED, message).into_response() + } + err => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(), + } +} + #[derive(Clone, Copy)] enum ArchiveAction { Archive, diff --git a/lib/crates/fabro-spa/assets/assets/chunk-7fpy4pmb.js b/lib/crates/fabro-spa/assets/assets/chunk-7fpy4pmb.js deleted file mode 100644 index 271497c14..000000000 --- a/lib/crates/fabro-spa/assets/assets/chunk-7fpy4pmb.js +++ /dev/null @@ -1 +0,0 @@ -import"./chunk-q07bg6gn.js";var e=Object.freeze(JSON.parse('{"name":"Pierre Dark","type":"dark","colors":{"editor.background":"#070707","editor.foreground":"#fbfbfb","foreground":"#fbfbfb","focusBorder":"#009fff","selection.background":"#19283c","editor.selectionBackground":"#009fff4d","editor.lineHighlightBackground":"#19283c8c","editorCursor.foreground":"#009fff","editorLineNumber.foreground":"#84848A","editorLineNumber.activeForeground":"#adadb1","editorIndentGuide.background":"#1F1F21","editorIndentGuide.activeBackground":"#2e2e30","diffEditor.insertedTextBackground":"#00cab11a","diffEditor.deletedTextBackground":"#ff2e3f1a","sideBar.background":"#141415","sideBar.foreground":"#adadb1","sideBar.border":"#070707","sideBarTitle.foreground":"#fbfbfb","sideBarSectionHeader.background":"#141415","sideBarSectionHeader.foreground":"#adadb1","sideBarSectionHeader.border":"#070707","activityBar.background":"#141415","activityBar.foreground":"#fbfbfb","activityBar.border":"#070707","activityBar.activeBorder":"#009fff","activityBarBadge.background":"#009fff","activityBarBadge.foreground":"#070707","titleBar.activeBackground":"#141415","titleBar.activeForeground":"#fbfbfb","titleBar.inactiveBackground":"#141415","titleBar.inactiveForeground":"#84848A","titleBar.border":"#070707","list.activeSelectionBackground":"#19283c99","list.activeSelectionForeground":"#fbfbfb","list.inactiveSelectionBackground":"#19283c73","list.hoverBackground":"#19283c59","list.focusOutline":"#009fff","tab.activeBackground":"#070707","tab.activeForeground":"#fbfbfb","tab.activeBorderTop":"#009fff","tab.inactiveBackground":"#141415","tab.inactiveForeground":"#84848A","tab.border":"#070707","editorGroupHeader.tabsBackground":"#141415","editorGroupHeader.tabsBorder":"#070707","panel.background":"#141415","panel.border":"#070707","panelTitle.activeBorder":"#009fff","panelTitle.activeForeground":"#fbfbfb","panelTitle.inactiveForeground":"#84848A","statusBar.background":"#141415","statusBar.foreground":"#adadb1","statusBar.border":"#070707","statusBar.noFolderBackground":"#141415","statusBar.debuggingBackground":"#ffca00","statusBar.debuggingForeground":"#070707","statusBarItem.remoteBackground":"#141415","statusBarItem.remoteForeground":"#adadb1","input.background":"#1F1F21","input.border":"#1F1F21","input.foreground":"#fbfbfb","input.placeholderForeground":"#79797F","dropdown.background":"#1F1F21","dropdown.border":"#1F1F21","dropdown.foreground":"#fbfbfb","button.background":"#009fff","button.foreground":"#070707","button.hoverBackground":"#0190e6","textLink.foreground":"#009fff","textLink.activeForeground":"#009fff","gitDecoration.addedResourceForeground":"#00cab1","gitDecoration.conflictingResourceForeground":"#ffca00","gitDecoration.modifiedResourceForeground":"#009fff","gitDecoration.deletedResourceForeground":"#ff2e3f","gitDecoration.untrackedResourceForeground":"#00cab1","gitDecoration.ignoredResourceForeground":"#84848A","terminal.titleForeground":"#adadb1","terminal.titleInactiveForeground":"#84848A","terminal.background":"#141415","terminal.foreground":"#adadb1","terminal.ansiBlack":"#141415","terminal.ansiRed":"#ff2e3f","terminal.ansiGreen":"#0dbe4e","terminal.ansiYellow":"#ffca00","terminal.ansiBlue":"#009fff","terminal.ansiMagenta":"#c635e4","terminal.ansiCyan":"#08c0ef","terminal.ansiWhite":"#c6c6c8","terminal.ansiBrightBlack":"#141415","terminal.ansiBrightRed":"#ff2e3f","terminal.ansiBrightGreen":"#0dbe4e","terminal.ansiBrightYellow":"#ffca00","terminal.ansiBrightBlue":"#009fff","terminal.ansiBrightMagenta":"#c635e4","terminal.ansiBrightCyan":"#08c0ef","terminal.ansiBrightWhite":"#c6c6c8"},"tokenColors":[{"scope":["comment","punctuation.definition.comment"],"settings":{"foreground":"#84848A"}},{"scope":"comment markup.link","settings":{"foreground":"#84848A"}},{"scope":["string","constant.other.symbol"],"settings":{"foreground":"#5ecc71"}},{"scope":["punctuation.definition.string.begin","punctuation.definition.string.end"],"settings":{"foreground":"#5ecc71"}},{"scope":["constant.numeric","constant.language.boolean"],"settings":{"foreground":"#68cdf2"}},{"scope":"constant","settings":{"foreground":"#ffd452"}},{"scope":"punctuation.definition.constant","settings":{"foreground":"#ffd452"}},{"scope":"constant.language","settings":{"foreground":"#68cdf2"}},{"scope":"variable.other.constant","settings":{"foreground":"#ffca00"}},{"scope":"keyword","settings":{"foreground":"#ff678d"}},{"scope":"keyword.control","settings":{"foreground":"#ff678d"}},{"scope":["storage","storage.type","storage.modifier"],"settings":{"foreground":"#ff678d"}},{"scope":"token.storage","settings":{"foreground":"#ff678d"}},{"scope":["keyword.operator.new","keyword.operator.expression.instanceof","keyword.operator.expression.typeof","keyword.operator.expression.void","keyword.operator.expression.delete","keyword.operator.expression.in","keyword.operator.expression.of","keyword.operator.expression.keyof"],"settings":{"foreground":"#ff678d"}},{"scope":"keyword.operator.delete","settings":{"foreground":"#ff678d"}},{"scope":["variable","identifier","meta.definition.variable"],"settings":{"foreground":"#ffa359"}},{"scope":["variable.other.readwrite","meta.object-literal.key","support.variable.property","support.variable.object.process","support.variable.object.node"],"settings":{"foreground":"#ffa359"}},{"scope":"variable.language","settings":{"foreground":"#ffca00"}},{"scope":"variable.parameter.function","settings":{"foreground":"#adadb1"}},{"scope":"function.parameter","settings":{"foreground":"#adadb1"}},{"scope":"variable.parameter","settings":{"foreground":"#adadb1"}},{"scope":"variable.parameter.function.language.python","settings":{"foreground":"#ffd452"}},{"scope":"variable.parameter.function.python","settings":{"foreground":"#ffd452"}},{"scope":["support.function","entity.name.function","meta.function-call","meta.require","support.function.any-method","variable.function"],"settings":{"foreground":"#9d6afb"}},{"scope":"keyword.other.special-method","settings":{"foreground":"#9d6afb"}},{"scope":"entity.name.function","settings":{"foreground":"#9d6afb"}},{"scope":"support.function.console","settings":{"foreground":"#9d6afb"}},{"scope":["support.type","entity.name.type","entity.name.class","storage.type"],"settings":{"foreground":"#d568ea"}},{"scope":["support.class","entity.name.type.class"],"settings":{"foreground":"#d568ea"}},{"scope":["entity.name.class","variable.other.class.js","variable.other.class.ts"],"settings":{"foreground":"#d568ea"}},{"scope":"entity.name.class.identifier.namespace.type","settings":{"foreground":"#d568ea"}},{"scope":"entity.name.type.namespace","settings":{"foreground":"#ffca00"}},{"scope":"entity.other.inherited-class","settings":{"foreground":"#d568ea"}},{"scope":"entity.name.namespace","settings":{"foreground":"#ffca00"}},{"scope":"keyword.operator","settings":{"foreground":"#79797F"}},{"scope":["keyword.operator.logical","keyword.operator.bitwise","keyword.operator.channel"],"settings":{"foreground":"#08c0ef"}},{"scope":["keyword.operator.arithmetic","keyword.operator.comparison","keyword.operator.relational","keyword.operator.increment","keyword.operator.decrement"],"settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.assignment","settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.assignment.compound","settings":{"foreground":"#ff678d"}},{"scope":["keyword.operator.assignment.compound.js","keyword.operator.assignment.compound.ts"],"settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.ternary","settings":{"foreground":"#ff678d"}},{"scope":"keyword.operator.optional","settings":{"foreground":"#ff678d"}},{"scope":"punctuation","settings":{"foreground":"#79797F"}},{"scope":"punctuation.separator.delimiter","settings":{"foreground":"#79797F"}},{"scope":"punctuation.separator.key-value","settings":{"foreground":"#79797F"}},{"scope":"punctuation.terminator","settings":{"foreground":"#79797F"}},{"scope":"meta.brace","settings":{"foreground":"#79797F"}},{"scope":"meta.brace.square","settings":{"foreground":"#79797F"}},{"scope":"meta.brace.round","settings":{"foreground":"#79797F"}},{"scope":"function.brace","settings":{"foreground":"#79797F"}},{"scope":["punctuation.definition.parameters","punctuation.definition.typeparameters"],"settings":{"foreground":"#79797F"}},{"scope":["punctuation.definition.block","punctuation.definition.tag"],"settings":{"foreground":"#79797F"}},{"scope":["meta.tag.tsx","meta.tag.jsx","meta.tag.js","meta.tag.ts"],"settings":{"foreground":"#79797F"}},{"scope":"keyword.operator.expression.import","settings":{"foreground":"#9d6afb"}},{"scope":"keyword.operator.module","settings":{"foreground":"#ff678d"}},{"scope":"support.type.object.console","settings":{"foreground":"#ffa359"}},{"scope":["support.module.node","support.type.object.module","entity.name.type.module"],"settings":{"foreground":"#ffca00"}},{"scope":"support.constant.math","settings":{"foreground":"#ffca00"}},{"scope":"support.constant.property.math","settings":{"foreground":"#ffd452"}},{"scope":"support.constant.json","settings":{"foreground":"#ffd452"}},{"scope":"support.type.object.dom","settings":{"foreground":"#08c0ef"}},{"scope":["support.variable.dom","support.variable.property.dom"],"settings":{"foreground":"#ffa359"}},{"scope":"support.variable.property.process","settings":{"foreground":"#ffd452"}},{"scope":"meta.property.object","settings":{"foreground":"#ffa359"}},{"scope":"variable.parameter.function.js","settings":{"foreground":"#ffa359"}},{"scope":["keyword.other.template.begin","keyword.other.template.end"],"settings":{"foreground":"#5ecc71"}},{"scope":["keyword.other.substitution.begin","keyword.other.substitution.end"],"settings":{"foreground":"#5ecc71"}},{"scope":["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end"],"settings":{"foreground":"#ff678d"}},{"scope":"meta.template.expression","settings":{"foreground":"#79797F"}},{"scope":"punctuation.section.embedded","settings":{"foreground":"#ffa359"}},{"scope":"variable.interpolation","settings":{"foreground":"#ffa359"}},{"scope":["punctuation.section.embedded.begin","punctuation.section.embedded.end"],"settings":{"foreground":"#ff678d"}},{"scope":"punctuation.quasi.element","settings":{"foreground":"#ff678d"}},{"scope":["support.type.primitive.ts","support.type.builtin.ts","support.type.primitive.tsx","support.type.builtin.tsx"],"settings":{"foreground":"#d568ea"}},{"scope":"support.type.type.flowtype","settings":{"foreground":"#9d6afb"}},{"scope":"support.type.primitive","settings":{"foreground":"#d568ea"}},{"scope":"support.variable.magic.python","settings":{"foreground":"#ff6762"}},{"scope":"variable.parameter.function.language.special.self.python","settings":{"foreground":"#ffca00"}},{"scope":["punctuation.separator.period.python","punctuation.separator.element.python","punctuation.parenthesis.begin.python","punctuation.parenthesis.end.python"],"settings":{"foreground":"#79797F"}},{"scope":["punctuation.definition.arguments.begin.python","punctuation.definition.arguments.end.python","punctuation.separator.arguments.python","punctuation.definition.list.begin.python","punctuation.definition.list.end.python"],"settings":{"foreground":"#79797F"}},{"scope":"support.type.python","settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.logical.python","settings":{"foreground":"#ff678d"}},{"scope":"meta.function-call.generic.python","settings":{"foreground":"#9d6afb"}},{"scope":"constant.character.format.placeholder.other.python","settings":{"foreground":"#ffd452"}},{"scope":"meta.function.decorator.python","settings":{"foreground":"#9d6afb"}},{"scope":["support.token.decorator.python","meta.function.decorator.identifier.python"],"settings":{"foreground":"#08c0ef"}},{"scope":"storage.modifier.lifetime.rust","settings":{"foreground":"#79797F"}},{"scope":"support.function.std.rust","settings":{"foreground":"#9d6afb"}},{"scope":"entity.name.lifetime.rust","settings":{"foreground":"#ffca00"}},{"scope":"variable.language.rust","settings":{"foreground":"#ff6762"}},{"scope":"keyword.operator.misc.rust","settings":{"foreground":"#79797F"}},{"scope":"keyword.operator.sigil.rust","settings":{"foreground":"#ff678d"}},{"scope":"support.constant.core.rust","settings":{"foreground":"#ffd452"}},{"scope":["meta.function.c","meta.function.cpp"],"settings":{"foreground":"#ff6762"}},{"scope":["punctuation.section.block.begin.bracket.curly.cpp","punctuation.section.block.end.bracket.curly.cpp","punctuation.terminator.statement.c","punctuation.section.block.begin.bracket.curly.c","punctuation.section.block.end.bracket.curly.c","punctuation.section.parens.begin.bracket.round.c","punctuation.section.parens.end.bracket.round.c","punctuation.section.parameters.begin.bracket.round.c","punctuation.section.parameters.end.bracket.round.c"],"settings":{"foreground":"#79797F"}},{"scope":["keyword.operator.assignment.c","keyword.operator.comparison.c","keyword.operator.c","keyword.operator.increment.c","keyword.operator.decrement.c","keyword.operator.bitwise.shift.c"],"settings":{"foreground":"#ff678d"}},{"scope":["keyword.operator.assignment.cpp","keyword.operator.comparison.cpp","keyword.operator.cpp","keyword.operator.increment.cpp","keyword.operator.decrement.cpp","keyword.operator.bitwise.shift.cpp"],"settings":{"foreground":"#ff678d"}},{"scope":["punctuation.separator.c","punctuation.separator.cpp"],"settings":{"foreground":"#ff678d"}},{"scope":["support.type.posix-reserved.c","support.type.posix-reserved.cpp"],"settings":{"foreground":"#08c0ef"}},{"scope":["keyword.operator.sizeof.c","keyword.operator.sizeof.cpp"],"settings":{"foreground":"#ff678d"}},{"scope":"variable.c","settings":{"foreground":"#79797F"}},{"scope":["storage.type.annotation.java","storage.type.object.array.java"],"settings":{"foreground":"#ffca00"}},{"scope":"source.java","settings":{"foreground":"#ff6762"}},{"scope":["punctuation.section.block.begin.java","punctuation.section.block.end.java","punctuation.definition.method-parameters.begin.java","punctuation.definition.method-parameters.end.java","meta.method.identifier.java","punctuation.section.method.begin.java","punctuation.section.method.end.java","punctuation.terminator.java","punctuation.section.class.begin.java","punctuation.section.class.end.java","punctuation.section.inner-class.begin.java","punctuation.section.inner-class.end.java","meta.method-call.java","punctuation.section.class.begin.bracket.curly.java","punctuation.section.class.end.bracket.curly.java","punctuation.section.method.begin.bracket.curly.java","punctuation.section.method.end.bracket.curly.java","punctuation.separator.period.java","punctuation.bracket.angle.java","punctuation.definition.annotation.java","meta.method.body.java"],"settings":{"foreground":"#79797F"}},{"scope":"meta.method.java","settings":{"foreground":"#9d6afb"}},{"scope":["storage.modifier.import.java","storage.type.java","storage.type.generic.java"],"settings":{"foreground":"#ffca00"}},{"scope":"keyword.operator.instanceof.java","settings":{"foreground":"#ff678d"}},{"scope":"meta.definition.variable.name.java","settings":{"foreground":"#ff6762"}},{"scope":"token.variable.parameter.java","settings":{"foreground":"#79797F"}},{"scope":"import.storage.java","settings":{"foreground":"#ffca00"}},{"scope":"token.package.keyword","settings":{"foreground":"#ff678d"}},{"scope":"token.package","settings":{"foreground":"#79797F"}},{"scope":"token.storage.type.java","settings":{"foreground":"#ffca00"}},{"scope":"keyword.operator.assignment.go","settings":{"foreground":"#ffca00"}},{"scope":["keyword.operator.arithmetic.go","keyword.operator.address.go"],"settings":{"foreground":"#ff678d"}},{"scope":"entity.name.package.go","settings":{"foreground":"#ffca00"}},{"scope":["support.other.namespace.use.php","support.other.namespace.use-as.php","support.other.namespace.php","entity.other.alias.php","meta.interface.php"],"settings":{"foreground":"#ffca00"}},{"scope":"keyword.operator.error-control.php","settings":{"foreground":"#ff678d"}},{"scope":"keyword.operator.type.php","settings":{"foreground":"#ff678d"}},{"scope":["punctuation.section.array.begin.php","punctuation.section.array.end.php"],"settings":{"foreground":"#79797F"}},{"scope":["storage.type.php","meta.other.type.phpdoc.php","keyword.other.type.php","keyword.other.array.phpdoc.php"],"settings":{"foreground":"#ffca00"}},{"scope":["meta.function-call.php","meta.function-call.object.php","meta.function-call.static.php"],"settings":{"foreground":"#9d6afb"}},{"scope":["punctuation.definition.parameters.begin.bracket.round.php","punctuation.definition.parameters.end.bracket.round.php","punctuation.separator.delimiter.php","punctuation.section.scope.begin.php","punctuation.section.scope.end.php","punctuation.terminator.expression.php","punctuation.definition.arguments.begin.bracket.round.php","punctuation.definition.arguments.end.bracket.round.php","punctuation.definition.storage-type.begin.bracket.round.php","punctuation.definition.storage-type.end.bracket.round.php","punctuation.definition.array.begin.bracket.round.php","punctuation.definition.array.end.bracket.round.php","punctuation.definition.begin.bracket.round.php","punctuation.definition.end.bracket.round.php","punctuation.definition.begin.bracket.curly.php","punctuation.definition.end.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php","punctuation.definition.section.switch-block.start.bracket.curly.php","punctuation.definition.section.switch-block.begin.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php"],"settings":{"foreground":"#79797F"}},{"scope":["support.constant.ext.php","support.constant.std.php","support.constant.core.php","support.constant.parser-token.php"],"settings":{"foreground":"#ffd452"}},{"scope":["entity.name.goto-label.php","support.other.php"],"settings":{"foreground":"#9d6afb"}},{"scope":["keyword.operator.logical.php","keyword.operator.bitwise.php","keyword.operator.arithmetic.php"],"settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.regexp.php","settings":{"foreground":"#ff678d"}},{"scope":"keyword.operator.comparison.php","settings":{"foreground":"#08c0ef"}},{"scope":["keyword.operator.heredoc.php","keyword.operator.nowdoc.php"],"settings":{"foreground":"#ff678d"}},{"scope":"variable.other.class.php","settings":{"foreground":"#ff6762"}},{"scope":"invalid.illegal.non-null-typehinted.php","settings":{"foreground":"#f44747"}},{"scope":"variable.other.generic-type.haskell","settings":{"foreground":"#ff678d"}},{"scope":"storage.type.haskell","settings":{"foreground":"#ffd452"}},{"scope":"storage.type.cs","settings":{"foreground":"#ffca00"}},{"scope":"entity.name.variable.local.cs","settings":{"foreground":"#ff6762"}},{"scope":"entity.name.label.cs","settings":{"foreground":"#ffca00"}},{"scope":["entity.name.scope-resolution.function.call","entity.name.scope-resolution.function.definition"],"settings":{"foreground":"#ffca00"}},{"scope":["punctuation.definition.delayed.unison","punctuation.definition.list.begin.unison","punctuation.definition.list.end.unison","punctuation.definition.ability.begin.unison","punctuation.definition.ability.end.unison","punctuation.operator.assignment.as.unison","punctuation.separator.pipe.unison","punctuation.separator.delimiter.unison","punctuation.definition.hash.unison"],"settings":{"foreground":"#ff6762"}},{"scope":"support.constant.edge","settings":{"foreground":"#ff678d"}},{"scope":"support.type.prelude.elm","settings":{"foreground":"#08c0ef"}},{"scope":"support.constant.elm","settings":{"foreground":"#ffd452"}},{"scope":"entity.global.clojure","settings":{"foreground":"#ffca00"}},{"scope":"meta.symbol.clojure","settings":{"foreground":"#ff6762"}},{"scope":"constant.keyword.clojure","settings":{"foreground":"#08c0ef"}},{"scope":["meta.arguments.coffee","variable.parameter.function.coffee"],"settings":{"foreground":"#ff6762"}},{"scope":"storage.modifier.import.groovy","settings":{"foreground":"#ffca00"}},{"scope":"meta.method.groovy","settings":{"foreground":"#9d6afb"}},{"scope":"meta.definition.variable.name.groovy","settings":{"foreground":"#ff6762"}},{"scope":"meta.definition.class.inherited.classes.groovy","settings":{"foreground":"#5ecc71"}},{"scope":"support.variable.semantic.hlsl","settings":{"foreground":"#ffca00"}},{"scope":["support.type.texture.hlsl","support.type.sampler.hlsl","support.type.object.hlsl","support.type.object.rw.hlsl","support.type.fx.hlsl","support.type.object.hlsl"],"settings":{"foreground":"#ff678d"}},{"scope":["text.variable","text.bracketed"],"settings":{"foreground":"#ff6762"}},{"scope":["support.type.swift","support.type.vb.asp"],"settings":{"foreground":"#ffca00"}},{"scope":"meta.scope.prerequisites.makefile","settings":{"foreground":"#ff6762"}},{"scope":"source.makefile","settings":{"foreground":"#ffca00"}},{"scope":"source.ini","settings":{"foreground":"#5ecc71"}},{"scope":"constant.language.symbol.ruby","settings":{"foreground":"#08c0ef"}},{"scope":["function.parameter.ruby","function.parameter.cs"],"settings":{"foreground":"#79797F"}},{"scope":"constant.language.symbol.elixir","settings":{"foreground":"#08c0ef"}},{"scope":"text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade","settings":{"foreground":"#ff678d"}},{"scope":"text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade","settings":{"foreground":"#ff678d"}},{"scope":"entity.name.function.xi","settings":{"foreground":"#ffca00"}},{"scope":"entity.name.class.xi","settings":{"foreground":"#08c0ef"}},{"scope":"constant.character.character-class.regexp.xi","settings":{"foreground":"#ff6762"}},{"scope":"constant.regexp.xi","settings":{"foreground":"#ff678d"}},{"scope":"keyword.control.xi","settings":{"foreground":"#08c0ef"}},{"scope":"invalid.xi","settings":{"foreground":"#79797F"}},{"scope":"beginning.punctuation.definition.quote.markdown.xi","settings":{"foreground":"#5ecc71"}},{"scope":"beginning.punctuation.definition.list.markdown.xi","settings":{"foreground":"#84848A"}},{"scope":"constant.character.xi","settings":{"foreground":"#9d6afb"}},{"scope":"accent.xi","settings":{"foreground":"#9d6afb"}},{"scope":"wikiword.xi","settings":{"foreground":"#ffd452"}},{"scope":"constant.other.color.rgb-value.xi","settings":{"foreground":"#fbfbfb"}},{"scope":"punctuation.definition.tag.xi","settings":{"foreground":"#84848A"}},{"scope":["support.constant.property-value.scss","support.constant.property-value.css"],"settings":{"foreground":"#ffd452"}},{"scope":["keyword.operator.css","keyword.operator.scss","keyword.operator.less"],"settings":{"foreground":"#08c0ef"}},{"scope":["support.constant.color.w3c-standard-color-name.css","support.constant.color.w3c-standard-color-name.scss"],"settings":{"foreground":"#ffd452"}},{"scope":"punctuation.separator.list.comma.css","settings":{"foreground":"#79797F"}},{"scope":"support.type.vendored.property-name.css","settings":{"foreground":"#08c0ef"}},{"scope":"support.type.property-name.css","settings":{"foreground":"#08c0ef"}},{"scope":"support.type.property-name","settings":{"foreground":"#79797F"}},{"scope":"support.constant.property-value","settings":{"foreground":"#79797F"}},{"scope":"support.constant.font-name","settings":{"foreground":"#ffd452"}},{"scope":"entity.other.attribute-name.class.css","settings":{"foreground":"#61d5c0","fontStyle":"normal"}},{"scope":"entity.other.attribute-name.id","settings":{"foreground":"#9d6afb","fontStyle":"normal"}},{"scope":["entity.other.attribute-name.pseudo-element","entity.other.attribute-name.pseudo-class"],"settings":{"foreground":"#08c0ef"}},{"scope":"meta.selector","settings":{"foreground":"#ff678d"}},{"scope":"selector.sass","settings":{"foreground":"#ff6762"}},{"scope":"rgb-value","settings":{"foreground":"#08c0ef"}},{"scope":"inline-color-decoration rgb-value","settings":{"foreground":"#ffd452"}},{"scope":"less rgb-value","settings":{"foreground":"#ffd452"}},{"scope":"control.elements","settings":{"foreground":"#ffd452"}},{"scope":"keyword.operator.less","settings":{"foreground":"#ffd452"}},{"scope":"entity.name.tag","settings":{"foreground":"#ff6762"}},{"scope":"entity.other.attribute-name","settings":{"foreground":"#61d5c0","fontStyle":"normal"}},{"scope":"constant.character.entity","settings":{"foreground":"#ff6762"}},{"scope":"meta.tag","settings":{"foreground":"#79797F"}},{"scope":"invalid.illegal.bad-ampersand.html","settings":{"foreground":"#79797F"}},{"scope":"markup.heading","settings":{"foreground":"#ff6762"}},{"scope":["markup.heading punctuation.definition.heading","entity.name.section"],"settings":{"foreground":"#9d6afb"}},{"scope":"entity.name.section.markdown","settings":{"foreground":"#ff6762"}},{"scope":"punctuation.definition.heading.markdown","settings":{"foreground":"#ff6762"}},{"scope":"markup.heading.setext","settings":{"foreground":"#79797F"}},{"scope":["markup.heading.setext.1.markdown","markup.heading.setext.2.markdown"],"settings":{"foreground":"#ff6762"}},{"scope":["markup.bold","todo.bold"],"settings":{"foreground":"#ffd452"}},{"scope":"punctuation.definition.bold","settings":{"foreground":"#ffca00"}},{"scope":"punctuation.definition.bold.markdown","settings":{"foreground":"#ffd452"}},{"scope":["markup.italic","punctuation.definition.italic","todo.emphasis"],"settings":{"foreground":"#ff678d","fontStyle":"italic"}},{"scope":"emphasis md","settings":{"foreground":"#ff678d"}},{"scope":"markup.italic.markdown","settings":{"fontStyle":"italic"}},{"scope":["markup.underline.link.markdown","markup.underline.link.image.markdown"],"settings":{"foreground":"#ff678d"}},{"scope":["string.other.link.title.markdown","string.other.link.description.markdown"],"settings":{"foreground":"#9d6afb"}},{"scope":"punctuation.definition.metadata.markdown","settings":{"foreground":"#ff6762"}},{"scope":["markup.inline.raw.markdown","markup.inline.raw.string.markdown"],"settings":{"foreground":"#5ecc71"}},{"scope":"punctuation.definition.list.begin.markdown","settings":{"foreground":"#ff6762"}},{"scope":"punctuation.definition.list.markdown","settings":{"foreground":"#ff6762"}},{"scope":"beginning.punctuation.definition.list.markdown","settings":{"foreground":"#ff6762"}},{"scope":["punctuation.definition.string.begin.markdown","punctuation.definition.string.end.markdown"],"settings":{"foreground":"#ff6762"}},{"scope":"markup.quote.markdown","settings":{"foreground":"#84848A"}},{"scope":"keyword.other.unit","settings":{"foreground":"#ff6762"}},{"scope":"markup.changed.diff","settings":{"foreground":"#ffca00"}},{"scope":["meta.diff.header.from-file","meta.diff.header.to-file","punctuation.definition.from-file.diff","punctuation.definition.to-file.diff"],"settings":{"foreground":"#9d6afb"}},{"scope":"markup.inserted.diff","settings":{"foreground":"#5ecc71"}},{"scope":"markup.deleted.diff","settings":{"foreground":"#ff6762"}},{"scope":"string.regexp","settings":{"foreground":"#64d1db"}},{"scope":"constant.other.character-class.regexp","settings":{"foreground":"#ff6762"}},{"scope":"keyword.operator.quantifier.regexp","settings":{"foreground":"#ffd452"}},{"scope":"constant.character.escape","settings":{"foreground":"#68cdf2"}},{"scope":"source.json meta.structure.dictionary.json > string.quoted.json","settings":{"foreground":"#ff6762"}},{"scope":"source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string","settings":{"foreground":"#ff6762"}},{"scope":["source.json meta.structure.dictionary.json > value.json > string.quoted.json","source.json meta.structure.array.json > value.json > string.quoted.json","source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation","source.json meta.structure.array.json > value.json > string.quoted.json > punctuation"],"settings":{"foreground":"#5ecc71"}},{"scope":["source.json meta.structure.dictionary.json > constant.language.json","source.json meta.structure.array.json > constant.language.json"],"settings":{"foreground":"#08c0ef"}},{"scope":"support.type.property-name.json","settings":{"foreground":"#ff6762"}},{"scope":"support.type.property-name.json punctuation","settings":{"foreground":"#ff6762"}},{"scope":"punctuation.definition.block.sequence.item.yaml","settings":{"foreground":"#79797F"}},{"scope":"block.scope.end","settings":{"foreground":"#79797F"}},{"scope":"block.scope.begin","settings":{"foreground":"#79797F"}},{"scope":"token.info-token","settings":{"foreground":"#9d6afb"}},{"scope":"token.warn-token","settings":{"foreground":"#ffd452"}},{"scope":"token.error-token","settings":{"foreground":"#f44747"}},{"scope":"token.debug-token","settings":{"foreground":"#ff678d"}},{"scope":"invalid.illegal","settings":{"foreground":"#fbfbfb"}},{"scope":"invalid.broken","settings":{"foreground":"#fbfbfb"}},{"scope":"invalid.deprecated","settings":{"foreground":"#fbfbfb"}},{"scope":"invalid.unimplemented","settings":{"foreground":"#fbfbfb"}}],"semanticTokenColors":{"comment":"#84848A","string":"#5ecc71","number":"#68cdf2","regexp":"#64d1db","keyword":"#ff678d","variable":"#ffa359","parameter":"#adadb1","property":"#ffa359","function":"#9d6afb","method":"#9d6afb","type":"#d568ea","class":"#d568ea","namespace":"#ffca00","enumMember":"#08c0ef","variable.constant":"#ffd452","variable.defaultLibrary":"#ffca00"}}'));export{e as default}; diff --git a/lib/crates/fabro-spa/assets/assets/chunk-nghb2mxb.js b/lib/crates/fabro-spa/assets/assets/chunk-nghb2mxb.js deleted file mode 100644 index db154f1f0..000000000 --- a/lib/crates/fabro-spa/assets/assets/chunk-nghb2mxb.js +++ /dev/null @@ -1 +0,0 @@ -import"./chunk-q07bg6gn.js";var e=Object.freeze(JSON.parse('{"name":"Pierre Light","type":"light","colors":{"editor.background":"#ffffff","editor.foreground":"#070707","foreground":"#070707","focusBorder":"#009fff","selection.background":"#dfebff","editor.selectionBackground":"#009fff2e","editor.lineHighlightBackground":"#dfebff8c","editorCursor.foreground":"#009fff","editorLineNumber.foreground":"#84848A","editorLineNumber.activeForeground":"#6C6C71","editorIndentGuide.background":"#eeeeef","editorIndentGuide.activeBackground":"#dbdbdd","diffEditor.insertedTextBackground":"#00cab133","diffEditor.deletedTextBackground":"#ff2e3f33","sideBar.background":"#f8f8f8","sideBar.foreground":"#6C6C71","sideBar.border":"#eeeeef","sideBarTitle.foreground":"#070707","sideBarSectionHeader.background":"#f8f8f8","sideBarSectionHeader.foreground":"#6C6C71","sideBarSectionHeader.border":"#eeeeef","activityBar.background":"#f8f8f8","activityBar.foreground":"#070707","activityBar.border":"#eeeeef","activityBar.activeBorder":"#009fff","activityBarBadge.background":"#009fff","activityBarBadge.foreground":"#ffffff","titleBar.activeBackground":"#f8f8f8","titleBar.activeForeground":"#070707","titleBar.inactiveBackground":"#f8f8f8","titleBar.inactiveForeground":"#84848A","titleBar.border":"#eeeeef","list.activeSelectionBackground":"#dfebffcc","list.activeSelectionForeground":"#070707","list.inactiveSelectionBackground":"#dfebff73","list.hoverBackground":"#dfebff59","list.focusOutline":"#009fff","tab.activeBackground":"#ffffff","tab.activeForeground":"#070707","tab.activeBorderTop":"#009fff","tab.inactiveBackground":"#f8f8f8","tab.inactiveForeground":"#84848A","tab.border":"#eeeeef","editorGroupHeader.tabsBackground":"#f8f8f8","editorGroupHeader.tabsBorder":"#eeeeef","panel.background":"#f8f8f8","panel.border":"#eeeeef","panelTitle.activeBorder":"#009fff","panelTitle.activeForeground":"#070707","panelTitle.inactiveForeground":"#84848A","statusBar.background":"#f8f8f8","statusBar.foreground":"#6C6C71","statusBar.border":"#eeeeef","statusBar.noFolderBackground":"#f8f8f8","statusBar.debuggingBackground":"#ffca00","statusBar.debuggingForeground":"#ffffff","statusBarItem.remoteBackground":"#f8f8f8","statusBarItem.remoteForeground":"#6C6C71","input.background":"#f2f2f3","input.border":"#dbdbdd","input.foreground":"#070707","input.placeholderForeground":"#8E8E95","dropdown.background":"#f2f2f3","dropdown.border":"#dbdbdd","dropdown.foreground":"#070707","button.background":"#009fff","button.foreground":"#ffffff","button.hoverBackground":"#1aa9ff","textLink.foreground":"#009fff","textLink.activeForeground":"#009fff","gitDecoration.addedResourceForeground":"#00cab1","gitDecoration.conflictingResourceForeground":"#ffca00","gitDecoration.modifiedResourceForeground":"#009fff","gitDecoration.deletedResourceForeground":"#ff2e3f","gitDecoration.untrackedResourceForeground":"#00cab1","gitDecoration.ignoredResourceForeground":"#84848A","terminal.titleForeground":"#6C6C71","terminal.titleInactiveForeground":"#84848A","terminal.background":"#f8f8f8","terminal.foreground":"#6C6C71","terminal.ansiBlack":"#1F1F21","terminal.ansiRed":"#ff2e3f","terminal.ansiGreen":"#0dbe4e","terminal.ansiYellow":"#ffca00","terminal.ansiBlue":"#009fff","terminal.ansiMagenta":"#c635e4","terminal.ansiCyan":"#08c0ef","terminal.ansiWhite":"#c6c6c8","terminal.ansiBrightBlack":"#1F1F21","terminal.ansiBrightRed":"#ff2e3f","terminal.ansiBrightGreen":"#0dbe4e","terminal.ansiBrightYellow":"#ffca00","terminal.ansiBrightBlue":"#009fff","terminal.ansiBrightMagenta":"#c635e4","terminal.ansiBrightCyan":"#08c0ef","terminal.ansiBrightWhite":"#c6c6c8"},"tokenColors":[{"scope":["comment","punctuation.definition.comment"],"settings":{"foreground":"#84848A"}},{"scope":"comment markup.link","settings":{"foreground":"#84848A"}},{"scope":["string","constant.other.symbol"],"settings":{"foreground":"#199f43"}},{"scope":["punctuation.definition.string.begin","punctuation.definition.string.end"],"settings":{"foreground":"#199f43"}},{"scope":["constant.numeric","constant.language.boolean"],"settings":{"foreground":"#1ca1c7"}},{"scope":"constant","settings":{"foreground":"#d5a910"}},{"scope":"punctuation.definition.constant","settings":{"foreground":"#d5a910"}},{"scope":"constant.language","settings":{"foreground":"#1ca1c7"}},{"scope":"variable.other.constant","settings":{"foreground":"#d5a910"}},{"scope":"keyword","settings":{"foreground":"#fc2b73"}},{"scope":"keyword.control","settings":{"foreground":"#fc2b73"}},{"scope":["storage","storage.type","storage.modifier"],"settings":{"foreground":"#fc2b73"}},{"scope":"token.storage","settings":{"foreground":"#fc2b73"}},{"scope":["keyword.operator.new","keyword.operator.expression.instanceof","keyword.operator.expression.typeof","keyword.operator.expression.void","keyword.operator.expression.delete","keyword.operator.expression.in","keyword.operator.expression.of","keyword.operator.expression.keyof"],"settings":{"foreground":"#fc2b73"}},{"scope":"keyword.operator.delete","settings":{"foreground":"#fc2b73"}},{"scope":["variable","identifier","meta.definition.variable"],"settings":{"foreground":"#d47628"}},{"scope":["variable.other.readwrite","meta.object-literal.key","support.variable.property","support.variable.object.process","support.variable.object.node"],"settings":{"foreground":"#d47628"}},{"scope":"variable.language","settings":{"foreground":"#d5a910"}},{"scope":"variable.parameter.function","settings":{"foreground":"#79797F"}},{"scope":"function.parameter","settings":{"foreground":"#79797F"}},{"scope":"variable.parameter","settings":{"foreground":"#79797F"}},{"scope":"variable.parameter.function.language.python","settings":{"foreground":"#d5a910"}},{"scope":"variable.parameter.function.python","settings":{"foreground":"#d5a910"}},{"scope":["support.function","entity.name.function","meta.function-call","meta.require","support.function.any-method","variable.function"],"settings":{"foreground":"#7b43f8"}},{"scope":"keyword.other.special-method","settings":{"foreground":"#7b43f8"}},{"scope":"entity.name.function","settings":{"foreground":"#7b43f8"}},{"scope":"support.function.console","settings":{"foreground":"#7b43f8"}},{"scope":["support.type","entity.name.type","entity.name.class","storage.type"],"settings":{"foreground":"#c635e4"}},{"scope":["support.class","entity.name.type.class"],"settings":{"foreground":"#c635e4"}},{"scope":["entity.name.class","variable.other.class.js","variable.other.class.ts"],"settings":{"foreground":"#c635e4"}},{"scope":"entity.name.class.identifier.namespace.type","settings":{"foreground":"#c635e4"}},{"scope":"entity.name.type.namespace","settings":{"foreground":"#d5a910"}},{"scope":"entity.other.inherited-class","settings":{"foreground":"#c635e4"}},{"scope":"entity.name.namespace","settings":{"foreground":"#d5a910"}},{"scope":"keyword.operator","settings":{"foreground":"#79797F"}},{"scope":["keyword.operator.logical","keyword.operator.bitwise","keyword.operator.channel"],"settings":{"foreground":"#08c0ef"}},{"scope":["keyword.operator.arithmetic","keyword.operator.comparison","keyword.operator.relational","keyword.operator.increment","keyword.operator.decrement"],"settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.assignment","settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.assignment.compound","settings":{"foreground":"#fc2b73"}},{"scope":["keyword.operator.assignment.compound.js","keyword.operator.assignment.compound.ts"],"settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.ternary","settings":{"foreground":"#fc2b73"}},{"scope":"keyword.operator.optional","settings":{"foreground":"#fc2b73"}},{"scope":"punctuation","settings":{"foreground":"#79797F"}},{"scope":"punctuation.separator.delimiter","settings":{"foreground":"#79797F"}},{"scope":"punctuation.separator.key-value","settings":{"foreground":"#79797F"}},{"scope":"punctuation.terminator","settings":{"foreground":"#79797F"}},{"scope":"meta.brace","settings":{"foreground":"#79797F"}},{"scope":"meta.brace.square","settings":{"foreground":"#79797F"}},{"scope":"meta.brace.round","settings":{"foreground":"#79797F"}},{"scope":"function.brace","settings":{"foreground":"#79797F"}},{"scope":["punctuation.definition.parameters","punctuation.definition.typeparameters"],"settings":{"foreground":"#79797F"}},{"scope":["punctuation.definition.block","punctuation.definition.tag"],"settings":{"foreground":"#79797F"}},{"scope":["meta.tag.tsx","meta.tag.jsx","meta.tag.js","meta.tag.ts"],"settings":{"foreground":"#79797F"}},{"scope":"keyword.operator.expression.import","settings":{"foreground":"#7b43f8"}},{"scope":"keyword.operator.module","settings":{"foreground":"#fc2b73"}},{"scope":"support.type.object.console","settings":{"foreground":"#d47628"}},{"scope":["support.module.node","support.type.object.module","entity.name.type.module"],"settings":{"foreground":"#d5a910"}},{"scope":"support.constant.math","settings":{"foreground":"#d5a910"}},{"scope":"support.constant.property.math","settings":{"foreground":"#d5a910"}},{"scope":"support.constant.json","settings":{"foreground":"#d5a910"}},{"scope":"support.type.object.dom","settings":{"foreground":"#08c0ef"}},{"scope":["support.variable.dom","support.variable.property.dom"],"settings":{"foreground":"#d47628"}},{"scope":"support.variable.property.process","settings":{"foreground":"#d5a910"}},{"scope":"meta.property.object","settings":{"foreground":"#d47628"}},{"scope":"variable.parameter.function.js","settings":{"foreground":"#d47628"}},{"scope":["keyword.other.template.begin","keyword.other.template.end"],"settings":{"foreground":"#199f43"}},{"scope":["keyword.other.substitution.begin","keyword.other.substitution.end"],"settings":{"foreground":"#199f43"}},{"scope":["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end"],"settings":{"foreground":"#fc2b73"}},{"scope":"meta.template.expression","settings":{"foreground":"#79797F"}},{"scope":"punctuation.section.embedded","settings":{"foreground":"#d47628"}},{"scope":"variable.interpolation","settings":{"foreground":"#d47628"}},{"scope":["punctuation.section.embedded.begin","punctuation.section.embedded.end"],"settings":{"foreground":"#fc2b73"}},{"scope":"punctuation.quasi.element","settings":{"foreground":"#fc2b73"}},{"scope":["support.type.primitive.ts","support.type.builtin.ts","support.type.primitive.tsx","support.type.builtin.tsx"],"settings":{"foreground":"#c635e4"}},{"scope":"support.type.type.flowtype","settings":{"foreground":"#7b43f8"}},{"scope":"support.type.primitive","settings":{"foreground":"#c635e4"}},{"scope":"support.variable.magic.python","settings":{"foreground":"#d52c36"}},{"scope":"variable.parameter.function.language.special.self.python","settings":{"foreground":"#d5a910"}},{"scope":["punctuation.separator.period.python","punctuation.separator.element.python","punctuation.parenthesis.begin.python","punctuation.parenthesis.end.python"],"settings":{"foreground":"#79797F"}},{"scope":["punctuation.definition.arguments.begin.python","punctuation.definition.arguments.end.python","punctuation.separator.arguments.python","punctuation.definition.list.begin.python","punctuation.definition.list.end.python"],"settings":{"foreground":"#79797F"}},{"scope":"support.type.python","settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.logical.python","settings":{"foreground":"#fc2b73"}},{"scope":"meta.function-call.generic.python","settings":{"foreground":"#7b43f8"}},{"scope":"constant.character.format.placeholder.other.python","settings":{"foreground":"#d5a910"}},{"scope":"meta.function.decorator.python","settings":{"foreground":"#7b43f8"}},{"scope":["support.token.decorator.python","meta.function.decorator.identifier.python"],"settings":{"foreground":"#08c0ef"}},{"scope":"storage.modifier.lifetime.rust","settings":{"foreground":"#79797F"}},{"scope":"support.function.std.rust","settings":{"foreground":"#7b43f8"}},{"scope":"entity.name.lifetime.rust","settings":{"foreground":"#d5a910"}},{"scope":"variable.language.rust","settings":{"foreground":"#d52c36"}},{"scope":"keyword.operator.misc.rust","settings":{"foreground":"#79797F"}},{"scope":"keyword.operator.sigil.rust","settings":{"foreground":"#fc2b73"}},{"scope":"support.constant.core.rust","settings":{"foreground":"#d5a910"}},{"scope":["meta.function.c","meta.function.cpp"],"settings":{"foreground":"#d52c36"}},{"scope":["punctuation.section.block.begin.bracket.curly.cpp","punctuation.section.block.end.bracket.curly.cpp","punctuation.terminator.statement.c","punctuation.section.block.begin.bracket.curly.c","punctuation.section.block.end.bracket.curly.c","punctuation.section.parens.begin.bracket.round.c","punctuation.section.parens.end.bracket.round.c","punctuation.section.parameters.begin.bracket.round.c","punctuation.section.parameters.end.bracket.round.c"],"settings":{"foreground":"#79797F"}},{"scope":["keyword.operator.assignment.c","keyword.operator.comparison.c","keyword.operator.c","keyword.operator.increment.c","keyword.operator.decrement.c","keyword.operator.bitwise.shift.c"],"settings":{"foreground":"#fc2b73"}},{"scope":["keyword.operator.assignment.cpp","keyword.operator.comparison.cpp","keyword.operator.cpp","keyword.operator.increment.cpp","keyword.operator.decrement.cpp","keyword.operator.bitwise.shift.cpp"],"settings":{"foreground":"#fc2b73"}},{"scope":["punctuation.separator.c","punctuation.separator.cpp"],"settings":{"foreground":"#fc2b73"}},{"scope":["support.type.posix-reserved.c","support.type.posix-reserved.cpp"],"settings":{"foreground":"#08c0ef"}},{"scope":["keyword.operator.sizeof.c","keyword.operator.sizeof.cpp"],"settings":{"foreground":"#fc2b73"}},{"scope":"variable.c","settings":{"foreground":"#79797F"}},{"scope":["storage.type.annotation.java","storage.type.object.array.java"],"settings":{"foreground":"#d5a910"}},{"scope":"source.java","settings":{"foreground":"#d52c36"}},{"scope":["punctuation.section.block.begin.java","punctuation.section.block.end.java","punctuation.definition.method-parameters.begin.java","punctuation.definition.method-parameters.end.java","meta.method.identifier.java","punctuation.section.method.begin.java","punctuation.section.method.end.java","punctuation.terminator.java","punctuation.section.class.begin.java","punctuation.section.class.end.java","punctuation.section.inner-class.begin.java","punctuation.section.inner-class.end.java","meta.method-call.java","punctuation.section.class.begin.bracket.curly.java","punctuation.section.class.end.bracket.curly.java","punctuation.section.method.begin.bracket.curly.java","punctuation.section.method.end.bracket.curly.java","punctuation.separator.period.java","punctuation.bracket.angle.java","punctuation.definition.annotation.java","meta.method.body.java"],"settings":{"foreground":"#79797F"}},{"scope":"meta.method.java","settings":{"foreground":"#7b43f8"}},{"scope":["storage.modifier.import.java","storage.type.java","storage.type.generic.java"],"settings":{"foreground":"#d5a910"}},{"scope":"keyword.operator.instanceof.java","settings":{"foreground":"#fc2b73"}},{"scope":"meta.definition.variable.name.java","settings":{"foreground":"#d52c36"}},{"scope":"token.variable.parameter.java","settings":{"foreground":"#79797F"}},{"scope":"import.storage.java","settings":{"foreground":"#d5a910"}},{"scope":"token.package.keyword","settings":{"foreground":"#fc2b73"}},{"scope":"token.package","settings":{"foreground":"#79797F"}},{"scope":"token.storage.type.java","settings":{"foreground":"#d5a910"}},{"scope":"keyword.operator.assignment.go","settings":{"foreground":"#d5a910"}},{"scope":["keyword.operator.arithmetic.go","keyword.operator.address.go"],"settings":{"foreground":"#fc2b73"}},{"scope":"entity.name.package.go","settings":{"foreground":"#d5a910"}},{"scope":["support.other.namespace.use.php","support.other.namespace.use-as.php","support.other.namespace.php","entity.other.alias.php","meta.interface.php"],"settings":{"foreground":"#d5a910"}},{"scope":"keyword.operator.error-control.php","settings":{"foreground":"#fc2b73"}},{"scope":"keyword.operator.type.php","settings":{"foreground":"#fc2b73"}},{"scope":["punctuation.section.array.begin.php","punctuation.section.array.end.php"],"settings":{"foreground":"#79797F"}},{"scope":["storage.type.php","meta.other.type.phpdoc.php","keyword.other.type.php","keyword.other.array.phpdoc.php"],"settings":{"foreground":"#d5a910"}},{"scope":["meta.function-call.php","meta.function-call.object.php","meta.function-call.static.php"],"settings":{"foreground":"#7b43f8"}},{"scope":["punctuation.definition.parameters.begin.bracket.round.php","punctuation.definition.parameters.end.bracket.round.php","punctuation.separator.delimiter.php","punctuation.section.scope.begin.php","punctuation.section.scope.end.php","punctuation.terminator.expression.php","punctuation.definition.arguments.begin.bracket.round.php","punctuation.definition.arguments.end.bracket.round.php","punctuation.definition.storage-type.begin.bracket.round.php","punctuation.definition.storage-type.end.bracket.round.php","punctuation.definition.array.begin.bracket.round.php","punctuation.definition.array.end.bracket.round.php","punctuation.definition.begin.bracket.round.php","punctuation.definition.end.bracket.round.php","punctuation.definition.begin.bracket.curly.php","punctuation.definition.end.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php","punctuation.definition.section.switch-block.start.bracket.curly.php","punctuation.definition.section.switch-block.begin.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php"],"settings":{"foreground":"#79797F"}},{"scope":["support.constant.ext.php","support.constant.std.php","support.constant.core.php","support.constant.parser-token.php"],"settings":{"foreground":"#d5a910"}},{"scope":["entity.name.goto-label.php","support.other.php"],"settings":{"foreground":"#7b43f8"}},{"scope":["keyword.operator.logical.php","keyword.operator.bitwise.php","keyword.operator.arithmetic.php"],"settings":{"foreground":"#08c0ef"}},{"scope":"keyword.operator.regexp.php","settings":{"foreground":"#fc2b73"}},{"scope":"keyword.operator.comparison.php","settings":{"foreground":"#08c0ef"}},{"scope":["keyword.operator.heredoc.php","keyword.operator.nowdoc.php"],"settings":{"foreground":"#fc2b73"}},{"scope":"variable.other.class.php","settings":{"foreground":"#d52c36"}},{"scope":"invalid.illegal.non-null-typehinted.php","settings":{"foreground":"#f44747"}},{"scope":"variable.other.generic-type.haskell","settings":{"foreground":"#fc2b73"}},{"scope":"storage.type.haskell","settings":{"foreground":"#d5a910"}},{"scope":"storage.type.cs","settings":{"foreground":"#d5a910"}},{"scope":"entity.name.variable.local.cs","settings":{"foreground":"#d52c36"}},{"scope":"entity.name.label.cs","settings":{"foreground":"#d5a910"}},{"scope":["entity.name.scope-resolution.function.call","entity.name.scope-resolution.function.definition"],"settings":{"foreground":"#d5a910"}},{"scope":["punctuation.definition.delayed.unison","punctuation.definition.list.begin.unison","punctuation.definition.list.end.unison","punctuation.definition.ability.begin.unison","punctuation.definition.ability.end.unison","punctuation.operator.assignment.as.unison","punctuation.separator.pipe.unison","punctuation.separator.delimiter.unison","punctuation.definition.hash.unison"],"settings":{"foreground":"#d52c36"}},{"scope":"support.constant.edge","settings":{"foreground":"#fc2b73"}},{"scope":"support.type.prelude.elm","settings":{"foreground":"#08c0ef"}},{"scope":"support.constant.elm","settings":{"foreground":"#d5a910"}},{"scope":"entity.global.clojure","settings":{"foreground":"#d5a910"}},{"scope":"meta.symbol.clojure","settings":{"foreground":"#d52c36"}},{"scope":"constant.keyword.clojure","settings":{"foreground":"#08c0ef"}},{"scope":["meta.arguments.coffee","variable.parameter.function.coffee"],"settings":{"foreground":"#d52c36"}},{"scope":"storage.modifier.import.groovy","settings":{"foreground":"#d5a910"}},{"scope":"meta.method.groovy","settings":{"foreground":"#7b43f8"}},{"scope":"meta.definition.variable.name.groovy","settings":{"foreground":"#d52c36"}},{"scope":"meta.definition.class.inherited.classes.groovy","settings":{"foreground":"#199f43"}},{"scope":"support.variable.semantic.hlsl","settings":{"foreground":"#d5a910"}},{"scope":["support.type.texture.hlsl","support.type.sampler.hlsl","support.type.object.hlsl","support.type.object.rw.hlsl","support.type.fx.hlsl","support.type.object.hlsl"],"settings":{"foreground":"#fc2b73"}},{"scope":["text.variable","text.bracketed"],"settings":{"foreground":"#d52c36"}},{"scope":["support.type.swift","support.type.vb.asp"],"settings":{"foreground":"#d5a910"}},{"scope":"meta.scope.prerequisites.makefile","settings":{"foreground":"#d52c36"}},{"scope":"source.makefile","settings":{"foreground":"#d5a910"}},{"scope":"source.ini","settings":{"foreground":"#199f43"}},{"scope":"constant.language.symbol.ruby","settings":{"foreground":"#08c0ef"}},{"scope":["function.parameter.ruby","function.parameter.cs"],"settings":{"foreground":"#79797F"}},{"scope":"constant.language.symbol.elixir","settings":{"foreground":"#08c0ef"}},{"scope":"text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade","settings":{"foreground":"#fc2b73"}},{"scope":"text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade","settings":{"foreground":"#fc2b73"}},{"scope":"entity.name.function.xi","settings":{"foreground":"#d5a910"}},{"scope":"entity.name.class.xi","settings":{"foreground":"#08c0ef"}},{"scope":"constant.character.character-class.regexp.xi","settings":{"foreground":"#d52c36"}},{"scope":"constant.regexp.xi","settings":{"foreground":"#fc2b73"}},{"scope":"keyword.control.xi","settings":{"foreground":"#08c0ef"}},{"scope":"invalid.xi","settings":{"foreground":"#79797F"}},{"scope":"beginning.punctuation.definition.quote.markdown.xi","settings":{"foreground":"#199f43"}},{"scope":"beginning.punctuation.definition.list.markdown.xi","settings":{"foreground":"#84848A"}},{"scope":"constant.character.xi","settings":{"foreground":"#7b43f8"}},{"scope":"accent.xi","settings":{"foreground":"#7b43f8"}},{"scope":"wikiword.xi","settings":{"foreground":"#d5a910"}},{"scope":"constant.other.color.rgb-value.xi","settings":{"foreground":"#070707"}},{"scope":"punctuation.definition.tag.xi","settings":{"foreground":"#84848A"}},{"scope":["support.constant.property-value.scss","support.constant.property-value.css"],"settings":{"foreground":"#d5a910"}},{"scope":["keyword.operator.css","keyword.operator.scss","keyword.operator.less"],"settings":{"foreground":"#08c0ef"}},{"scope":["support.constant.color.w3c-standard-color-name.css","support.constant.color.w3c-standard-color-name.scss"],"settings":{"foreground":"#d5a910"}},{"scope":"punctuation.separator.list.comma.css","settings":{"foreground":"#79797F"}},{"scope":"support.type.vendored.property-name.css","settings":{"foreground":"#08c0ef"}},{"scope":"support.type.property-name.css","settings":{"foreground":"#08c0ef"}},{"scope":"support.type.property-name","settings":{"foreground":"#79797F"}},{"scope":"support.constant.property-value","settings":{"foreground":"#79797F"}},{"scope":"support.constant.font-name","settings":{"foreground":"#d5a910"}},{"scope":"entity.other.attribute-name.class.css","settings":{"foreground":"#16a994","fontStyle":"normal"}},{"scope":"entity.other.attribute-name.id","settings":{"foreground":"#7b43f8","fontStyle":"normal"}},{"scope":["entity.other.attribute-name.pseudo-element","entity.other.attribute-name.pseudo-class"],"settings":{"foreground":"#08c0ef"}},{"scope":"meta.selector","settings":{"foreground":"#fc2b73"}},{"scope":"selector.sass","settings":{"foreground":"#d52c36"}},{"scope":"rgb-value","settings":{"foreground":"#08c0ef"}},{"scope":"inline-color-decoration rgb-value","settings":{"foreground":"#d5a910"}},{"scope":"less rgb-value","settings":{"foreground":"#d5a910"}},{"scope":"control.elements","settings":{"foreground":"#d5a910"}},{"scope":"keyword.operator.less","settings":{"foreground":"#d5a910"}},{"scope":"entity.name.tag","settings":{"foreground":"#d52c36"}},{"scope":"entity.other.attribute-name","settings":{"foreground":"#16a994","fontStyle":"normal"}},{"scope":"constant.character.entity","settings":{"foreground":"#d52c36"}},{"scope":"meta.tag","settings":{"foreground":"#79797F"}},{"scope":"invalid.illegal.bad-ampersand.html","settings":{"foreground":"#79797F"}},{"scope":"markup.heading","settings":{"foreground":"#d52c36"}},{"scope":["markup.heading punctuation.definition.heading","entity.name.section"],"settings":{"foreground":"#7b43f8"}},{"scope":"entity.name.section.markdown","settings":{"foreground":"#d52c36"}},{"scope":"punctuation.definition.heading.markdown","settings":{"foreground":"#d52c36"}},{"scope":"markup.heading.setext","settings":{"foreground":"#79797F"}},{"scope":["markup.heading.setext.1.markdown","markup.heading.setext.2.markdown"],"settings":{"foreground":"#d52c36"}},{"scope":["markup.bold","todo.bold"],"settings":{"foreground":"#d5a910"}},{"scope":"punctuation.definition.bold","settings":{"foreground":"#d5a910"}},{"scope":"punctuation.definition.bold.markdown","settings":{"foreground":"#d5a910"}},{"scope":["markup.italic","punctuation.definition.italic","todo.emphasis"],"settings":{"foreground":"#fc2b73","fontStyle":"italic"}},{"scope":"emphasis md","settings":{"foreground":"#fc2b73"}},{"scope":"markup.italic.markdown","settings":{"fontStyle":"italic"}},{"scope":["markup.underline.link.markdown","markup.underline.link.image.markdown"],"settings":{"foreground":"#fc2b73"}},{"scope":["string.other.link.title.markdown","string.other.link.description.markdown"],"settings":{"foreground":"#7b43f8"}},{"scope":"punctuation.definition.metadata.markdown","settings":{"foreground":"#d52c36"}},{"scope":["markup.inline.raw.markdown","markup.inline.raw.string.markdown"],"settings":{"foreground":"#199f43"}},{"scope":"punctuation.definition.list.begin.markdown","settings":{"foreground":"#d52c36"}},{"scope":"punctuation.definition.list.markdown","settings":{"foreground":"#d52c36"}},{"scope":"beginning.punctuation.definition.list.markdown","settings":{"foreground":"#d52c36"}},{"scope":["punctuation.definition.string.begin.markdown","punctuation.definition.string.end.markdown"],"settings":{"foreground":"#d52c36"}},{"scope":"markup.quote.markdown","settings":{"foreground":"#84848A"}},{"scope":"keyword.other.unit","settings":{"foreground":"#d52c36"}},{"scope":"markup.changed.diff","settings":{"foreground":"#d5a910"}},{"scope":["meta.diff.header.from-file","meta.diff.header.to-file","punctuation.definition.from-file.diff","punctuation.definition.to-file.diff"],"settings":{"foreground":"#7b43f8"}},{"scope":"markup.inserted.diff","settings":{"foreground":"#199f43"}},{"scope":"markup.deleted.diff","settings":{"foreground":"#d52c36"}},{"scope":"string.regexp","settings":{"foreground":"#17a5af"}},{"scope":"constant.other.character-class.regexp","settings":{"foreground":"#d52c36"}},{"scope":"keyword.operator.quantifier.regexp","settings":{"foreground":"#d5a910"}},{"scope":"constant.character.escape","settings":{"foreground":"#1ca1c7"}},{"scope":"source.json meta.structure.dictionary.json > string.quoted.json","settings":{"foreground":"#d52c36"}},{"scope":"source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string","settings":{"foreground":"#d52c36"}},{"scope":["source.json meta.structure.dictionary.json > value.json > string.quoted.json","source.json meta.structure.array.json > value.json > string.quoted.json","source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation","source.json meta.structure.array.json > value.json > string.quoted.json > punctuation"],"settings":{"foreground":"#199f43"}},{"scope":["source.json meta.structure.dictionary.json > constant.language.json","source.json meta.structure.array.json > constant.language.json"],"settings":{"foreground":"#08c0ef"}},{"scope":"support.type.property-name.json","settings":{"foreground":"#d52c36"}},{"scope":"support.type.property-name.json punctuation","settings":{"foreground":"#d52c36"}},{"scope":"punctuation.definition.block.sequence.item.yaml","settings":{"foreground":"#79797F"}},{"scope":"block.scope.end","settings":{"foreground":"#79797F"}},{"scope":"block.scope.begin","settings":{"foreground":"#79797F"}},{"scope":"token.info-token","settings":{"foreground":"#7b43f8"}},{"scope":"token.warn-token","settings":{"foreground":"#d5a910"}},{"scope":"token.error-token","settings":{"foreground":"#f44747"}},{"scope":"token.debug-token","settings":{"foreground":"#fc2b73"}},{"scope":"invalid.illegal","settings":{"foreground":"#070707"}},{"scope":"invalid.broken","settings":{"foreground":"#070707"}},{"scope":"invalid.deprecated","settings":{"foreground":"#070707"}},{"scope":"invalid.unimplemented","settings":{"foreground":"#070707"}}],"semanticTokenColors":{"comment":"#84848A","string":"#199f43","number":"#1ca1c7","regexp":"#17a5af","keyword":"#fc2b73","variable":"#d47628","parameter":"#79797F","property":"#d47628","function":"#7b43f8","method":"#7b43f8","type":"#c635e4","class":"#c635e4","namespace":"#d5a910","enumMember":"#08c0ef","variable.constant":"#d5a910","variable.defaultLibrary":"#d5a910"}}'));export{e as default}; diff --git a/lib/crates/fabro-spa/assets/assets/chunk-q2qv5qr4.js b/lib/crates/fabro-spa/assets/assets/chunk-q2qv5qr4.js new file mode 100644 index 000000000..8d5674785 --- /dev/null +++ b/lib/crates/fabro-spa/assets/assets/chunk-q2qv5qr4.js @@ -0,0 +1 @@ +import"./chunk-q07bg6gn.js";var b="pierre-dark",h="dark",q={"editor.background":"#070707","editor.foreground":"#fbfbfb",foreground:"#fbfbfb",focusBorder:"#009fff","selection.background":"#19283c","editor.selectionBackground":"#009fff4d","editor.lineHighlightBackground":"#19283c8c","editorCursor.foreground":"#009fff","editorLineNumber.foreground":"#84848A","editorLineNumber.activeForeground":"#adadb1","editorIndentGuide.background":"#39393c","editorIndentGuide.activeBackground":"#2e2e30","diffEditor.insertedTextBackground":"#00cab11a","diffEditor.deletedTextBackground":"#ff2e3f1a","sideBar.background":"#141415","sideBar.foreground":"#adadb1","sideBar.border":"#070707","sideBarTitle.foreground":"#fbfbfb","sideBarSectionHeader.background":"#141415","sideBarSectionHeader.foreground":"#adadb1","sideBarSectionHeader.border":"#070707","activityBar.background":"#141415","activityBar.foreground":"#fbfbfb","activityBar.border":"#070707","activityBar.activeBorder":"#009fff","activityBarBadge.background":"#009fff","activityBarBadge.foreground":"#070707","titleBar.activeBackground":"#141415","titleBar.activeForeground":"#fbfbfb","titleBar.inactiveBackground":"#141415","titleBar.inactiveForeground":"#84848A","titleBar.border":"#070707","list.activeSelectionBackground":"#19283c99","list.activeSelectionForeground":"#fbfbfb","list.inactiveSelectionBackground":"#19283c73","list.hoverBackground":"#19283c59","list.focusOutline":"#009fff","tab.activeBackground":"#070707","tab.activeForeground":"#fbfbfb","tab.activeBorderTop":"#009fff","tab.inactiveBackground":"#141415","tab.inactiveForeground":"#84848A","tab.border":"#070707","editorGroupHeader.tabsBackground":"#141415","editorGroupHeader.tabsBorder":"#070707","panel.background":"#141415","panel.border":"#070707","panelTitle.activeBorder":"#009fff","panelTitle.activeForeground":"#fbfbfb","panelTitle.inactiveForeground":"#84848A","statusBar.background":"#141415","statusBar.foreground":"#adadb1","statusBar.border":"#070707","statusBar.noFolderBackground":"#141415","statusBar.debuggingBackground":"#ffca00","statusBar.debuggingForeground":"#070707","statusBarItem.remoteBackground":"#141415","statusBarItem.remoteForeground":"#adadb1","input.background":"#1F1F21","input.border":"#424245","input.foreground":"#fbfbfb","input.placeholderForeground":"#79797F","dropdown.background":"#1F1F21","dropdown.border":"#424245","dropdown.foreground":"#fbfbfb","button.background":"#009fff","button.foreground":"#070707","button.hoverBackground":"#0190e6","textLink.foreground":"#009fff","textLink.activeForeground":"#009fff","gitDecoration.addedResourceForeground":"#00cab1","gitDecoration.conflictingResourceForeground":"#ffca00","gitDecoration.modifiedResourceForeground":"#009fff","gitDecoration.deletedResourceForeground":"#ff2e3f","gitDecoration.untrackedResourceForeground":"#00cab1","gitDecoration.ignoredResourceForeground":"#84848A","terminal.titleForeground":"#adadb1","terminal.titleInactiveForeground":"#84848A","terminal.background":"#141415","terminal.foreground":"#adadb1","terminal.ansiBlack":"#141415","terminal.ansiRed":"#ff2e3f","terminal.ansiGreen":"#0dbe4e","terminal.ansiYellow":"#ffca00","terminal.ansiBlue":"#009fff","terminal.ansiMagenta":"#c635e4","terminal.ansiCyan":"#08c0ef","terminal.ansiWhite":"#c6c6c8","terminal.ansiBrightBlack":"#141415","terminal.ansiBrightRed":"#ff2e3f","terminal.ansiBrightGreen":"#0dbe4e","terminal.ansiBrightYellow":"#ffca00","terminal.ansiBrightBlue":"#009fff","terminal.ansiBrightMagenta":"#c635e4","terminal.ansiBrightCyan":"#08c0ef","terminal.ansiBrightWhite":"#c6c6c8"},v=[{scope:["comment","punctuation.definition.comment"],settings:{foreground:"#84848A"}},{scope:"comment markup.link",settings:{foreground:"#84848A"}},{scope:["string","constant.other.symbol"],settings:{foreground:"#5ecc71"}},{scope:["punctuation.definition.string.begin","punctuation.definition.string.end"],settings:{foreground:"#5ecc71"}},{scope:["constant.numeric","constant.language.boolean"],settings:{foreground:"#68cdf2"}},{scope:"constant",settings:{foreground:"#ffd452"}},{scope:"punctuation.definition.constant",settings:{foreground:"#ffd452"}},{scope:"constant.language",settings:{foreground:"#68cdf2"}},{scope:"variable.other.constant",settings:{foreground:"#ffca00"}},{scope:"keyword",settings:{foreground:"#ff678d"}},{scope:"keyword.control",settings:{foreground:"#ff678d"}},{scope:["storage","storage.type","storage.modifier"],settings:{foreground:"#ff678d"}},{scope:"token.storage",settings:{foreground:"#ff678d"}},{scope:["keyword.operator.new","keyword.operator.expression.instanceof","keyword.operator.expression.typeof","keyword.operator.expression.void","keyword.operator.expression.delete","keyword.operator.expression.in","keyword.operator.expression.of","keyword.operator.expression.keyof"],settings:{foreground:"#ff678d"}},{scope:"keyword.operator.delete",settings:{foreground:"#ff678d"}},{scope:["variable","identifier","meta.definition.variable"],settings:{foreground:"#ffa359"}},{scope:["variable.other.readwrite","meta.object-literal.key","support.variable.property","support.variable.object.process","support.variable.object.node"],settings:{foreground:"#ffa359"}},{scope:"variable.language",settings:{foreground:"#ffca00"}},{scope:"variable.parameter.function",settings:{foreground:"#adadb1"}},{scope:"function.parameter",settings:{foreground:"#adadb1"}},{scope:"variable.parameter",settings:{foreground:"#adadb1"}},{scope:"variable.parameter.function.language.python",settings:{foreground:"#ffd452"}},{scope:"variable.parameter.function.python",settings:{foreground:"#ffd452"}},{scope:["support.function","entity.name.function","meta.function-call","meta.require","support.function.any-method","variable.function"],settings:{foreground:"#9d6afb"}},{scope:"keyword.other.special-method",settings:{foreground:"#9d6afb"}},{scope:"entity.name.function",settings:{foreground:"#9d6afb"}},{scope:"support.function.console",settings:{foreground:"#9d6afb"}},{scope:["support.type","entity.name.type","entity.name.class","storage.type"],settings:{foreground:"#d568ea"}},{scope:["support.class","entity.name.type.class"],settings:{foreground:"#d568ea"}},{scope:["entity.name.class","variable.other.class.js","variable.other.class.ts"],settings:{foreground:"#d568ea"}},{scope:"entity.name.class.identifier.namespace.type",settings:{foreground:"#d568ea"}},{scope:"entity.name.type.namespace",settings:{foreground:"#ffca00"}},{scope:"entity.other.inherited-class",settings:{foreground:"#d568ea"}},{scope:"entity.name.namespace",settings:{foreground:"#ffca00"}},{scope:"keyword.operator",settings:{foreground:"#79797F"}},{scope:["keyword.operator.logical","keyword.operator.bitwise","keyword.operator.channel"],settings:{foreground:"#08c0ef"}},{scope:["keyword.operator.arithmetic","keyword.operator.comparison","keyword.operator.relational","keyword.operator.increment","keyword.operator.decrement"],settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.assignment",settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.assignment.compound",settings:{foreground:"#ff678d"}},{scope:["keyword.operator.assignment.compound.js","keyword.operator.assignment.compound.ts"],settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.ternary",settings:{foreground:"#ff678d"}},{scope:"keyword.operator.optional",settings:{foreground:"#ff678d"}},{scope:"punctuation",settings:{foreground:"#79797F"}},{scope:"punctuation.separator.delimiter",settings:{foreground:"#79797F"}},{scope:"punctuation.separator.key-value",settings:{foreground:"#79797F"}},{scope:"punctuation.terminator",settings:{foreground:"#79797F"}},{scope:"meta.brace",settings:{foreground:"#79797F"}},{scope:"meta.brace.square",settings:{foreground:"#79797F"}},{scope:"meta.brace.round",settings:{foreground:"#79797F"}},{scope:"function.brace",settings:{foreground:"#79797F"}},{scope:["punctuation.definition.parameters","punctuation.definition.typeparameters"],settings:{foreground:"#79797F"}},{scope:["punctuation.definition.block","punctuation.definition.tag"],settings:{foreground:"#79797F"}},{scope:["meta.tag.tsx","meta.tag.jsx","meta.tag.js","meta.tag.ts"],settings:{foreground:"#79797F"}},{scope:"keyword.operator.expression.import",settings:{foreground:"#9d6afb"}},{scope:"keyword.operator.module",settings:{foreground:"#ff678d"}},{scope:"support.type.object.console",settings:{foreground:"#ffa359"}},{scope:["support.module.node","support.type.object.module","entity.name.type.module"],settings:{foreground:"#ffca00"}},{scope:"support.constant.math",settings:{foreground:"#ffca00"}},{scope:"support.constant.property.math",settings:{foreground:"#ffd452"}},{scope:"support.constant.json",settings:{foreground:"#ffd452"}},{scope:"support.type.object.dom",settings:{foreground:"#08c0ef"}},{scope:["support.variable.dom","support.variable.property.dom"],settings:{foreground:"#ffa359"}},{scope:"support.variable.property.process",settings:{foreground:"#ffd452"}},{scope:"meta.property.object",settings:{foreground:"#ffa359"}},{scope:"variable.parameter.function.js",settings:{foreground:"#ffa359"}},{scope:["keyword.other.template.begin","keyword.other.template.end"],settings:{foreground:"#5ecc71"}},{scope:["keyword.other.substitution.begin","keyword.other.substitution.end"],settings:{foreground:"#5ecc71"}},{scope:["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end"],settings:{foreground:"#ff678d"}},{scope:"meta.template.expression",settings:{foreground:"#79797F"}},{scope:"punctuation.section.embedded",settings:{foreground:"#ffa359"}},{scope:"variable.interpolation",settings:{foreground:"#ffa359"}},{scope:["punctuation.section.embedded.begin","punctuation.section.embedded.end"],settings:{foreground:"#ff678d"}},{scope:"punctuation.quasi.element",settings:{foreground:"#ff678d"}},{scope:["support.type.primitive.ts","support.type.builtin.ts","support.type.primitive.tsx","support.type.builtin.tsx"],settings:{foreground:"#d568ea"}},{scope:"support.type.type.flowtype",settings:{foreground:"#9d6afb"}},{scope:"support.type.primitive",settings:{foreground:"#d568ea"}},{scope:"support.variable.magic.python",settings:{foreground:"#ff6762"}},{scope:"variable.parameter.function.language.special.self.python",settings:{foreground:"#ffca00"}},{scope:["punctuation.separator.period.python","punctuation.separator.element.python","punctuation.parenthesis.begin.python","punctuation.parenthesis.end.python"],settings:{foreground:"#79797F"}},{scope:["punctuation.definition.arguments.begin.python","punctuation.definition.arguments.end.python","punctuation.separator.arguments.python","punctuation.definition.list.begin.python","punctuation.definition.list.end.python"],settings:{foreground:"#79797F"}},{scope:"support.type.python",settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.logical.python",settings:{foreground:"#ff678d"}},{scope:"meta.function-call.generic.python",settings:{foreground:"#9d6afb"}},{scope:"constant.character.format.placeholder.other.python",settings:{foreground:"#ffd452"}},{scope:"meta.function.decorator.python",settings:{foreground:"#9d6afb"}},{scope:["support.token.decorator.python","meta.function.decorator.identifier.python"],settings:{foreground:"#08c0ef"}},{scope:"storage.modifier.lifetime.rust",settings:{foreground:"#79797F"}},{scope:"support.function.std.rust",settings:{foreground:"#9d6afb"}},{scope:"entity.name.lifetime.rust",settings:{foreground:"#ffca00"}},{scope:"variable.language.rust",settings:{foreground:"#ff6762"}},{scope:"keyword.operator.misc.rust",settings:{foreground:"#79797F"}},{scope:"keyword.operator.sigil.rust",settings:{foreground:"#ff678d"}},{scope:"support.constant.core.rust",settings:{foreground:"#ffd452"}},{scope:["meta.function.c","meta.function.cpp"],settings:{foreground:"#ff6762"}},{scope:["punctuation.section.block.begin.bracket.curly.cpp","punctuation.section.block.end.bracket.curly.cpp","punctuation.terminator.statement.c","punctuation.section.block.begin.bracket.curly.c","punctuation.section.block.end.bracket.curly.c","punctuation.section.parens.begin.bracket.round.c","punctuation.section.parens.end.bracket.round.c","punctuation.section.parameters.begin.bracket.round.c","punctuation.section.parameters.end.bracket.round.c"],settings:{foreground:"#79797F"}},{scope:["keyword.operator.assignment.c","keyword.operator.comparison.c","keyword.operator.c","keyword.operator.increment.c","keyword.operator.decrement.c","keyword.operator.bitwise.shift.c"],settings:{foreground:"#ff678d"}},{scope:["keyword.operator.assignment.cpp","keyword.operator.comparison.cpp","keyword.operator.cpp","keyword.operator.increment.cpp","keyword.operator.decrement.cpp","keyword.operator.bitwise.shift.cpp"],settings:{foreground:"#ff678d"}},{scope:["punctuation.separator.c","punctuation.separator.cpp"],settings:{foreground:"#ff678d"}},{scope:["support.type.posix-reserved.c","support.type.posix-reserved.cpp"],settings:{foreground:"#08c0ef"}},{scope:["keyword.operator.sizeof.c","keyword.operator.sizeof.cpp"],settings:{foreground:"#ff678d"}},{scope:"variable.c",settings:{foreground:"#79797F"}},{scope:["storage.type.annotation.java","storage.type.object.array.java"],settings:{foreground:"#ffca00"}},{scope:"source.java",settings:{foreground:"#ff6762"}},{scope:["punctuation.section.block.begin.java","punctuation.section.block.end.java","punctuation.definition.method-parameters.begin.java","punctuation.definition.method-parameters.end.java","meta.method.identifier.java","punctuation.section.method.begin.java","punctuation.section.method.end.java","punctuation.terminator.java","punctuation.section.class.begin.java","punctuation.section.class.end.java","punctuation.section.inner-class.begin.java","punctuation.section.inner-class.end.java","meta.method-call.java","punctuation.section.class.begin.bracket.curly.java","punctuation.section.class.end.bracket.curly.java","punctuation.section.method.begin.bracket.curly.java","punctuation.section.method.end.bracket.curly.java","punctuation.separator.period.java","punctuation.bracket.angle.java","punctuation.definition.annotation.java","meta.method.body.java"],settings:{foreground:"#79797F"}},{scope:"meta.method.java",settings:{foreground:"#9d6afb"}},{scope:["storage.modifier.import.java","storage.type.java","storage.type.generic.java"],settings:{foreground:"#ffca00"}},{scope:"keyword.operator.instanceof.java",settings:{foreground:"#ff678d"}},{scope:"meta.definition.variable.name.java",settings:{foreground:"#ff6762"}},{scope:"token.variable.parameter.java",settings:{foreground:"#79797F"}},{scope:"import.storage.java",settings:{foreground:"#ffca00"}},{scope:"token.package.keyword",settings:{foreground:"#ff678d"}},{scope:"token.package",settings:{foreground:"#79797F"}},{scope:"token.storage.type.java",settings:{foreground:"#ffca00"}},{scope:"keyword.operator.assignment.go",settings:{foreground:"#ffca00"}},{scope:["keyword.operator.arithmetic.go","keyword.operator.address.go"],settings:{foreground:"#ff678d"}},{scope:"entity.name.package.go",settings:{foreground:"#ffca00"}},{scope:["support.other.namespace.use.php","support.other.namespace.use-as.php","support.other.namespace.php","entity.other.alias.php","meta.interface.php"],settings:{foreground:"#ffca00"}},{scope:"keyword.operator.error-control.php",settings:{foreground:"#ff678d"}},{scope:"keyword.operator.type.php",settings:{foreground:"#ff678d"}},{scope:["punctuation.section.array.begin.php","punctuation.section.array.end.php"],settings:{foreground:"#79797F"}},{scope:["storage.type.php","meta.other.type.phpdoc.php","keyword.other.type.php","keyword.other.array.phpdoc.php"],settings:{foreground:"#ffca00"}},{scope:["meta.function-call.php","meta.function-call.object.php","meta.function-call.static.php"],settings:{foreground:"#9d6afb"}},{scope:["punctuation.definition.parameters.begin.bracket.round.php","punctuation.definition.parameters.end.bracket.round.php","punctuation.separator.delimiter.php","punctuation.section.scope.begin.php","punctuation.section.scope.end.php","punctuation.terminator.expression.php","punctuation.definition.arguments.begin.bracket.round.php","punctuation.definition.arguments.end.bracket.round.php","punctuation.definition.storage-type.begin.bracket.round.php","punctuation.definition.storage-type.end.bracket.round.php","punctuation.definition.array.begin.bracket.round.php","punctuation.definition.array.end.bracket.round.php","punctuation.definition.begin.bracket.round.php","punctuation.definition.end.bracket.round.php","punctuation.definition.begin.bracket.curly.php","punctuation.definition.end.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php","punctuation.definition.section.switch-block.start.bracket.curly.php","punctuation.definition.section.switch-block.begin.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php"],settings:{foreground:"#79797F"}},{scope:["support.constant.ext.php","support.constant.std.php","support.constant.core.php","support.constant.parser-token.php"],settings:{foreground:"#ffd452"}},{scope:["entity.name.goto-label.php","support.other.php"],settings:{foreground:"#9d6afb"}},{scope:["keyword.operator.logical.php","keyword.operator.bitwise.php","keyword.operator.arithmetic.php"],settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.regexp.php",settings:{foreground:"#ff678d"}},{scope:"keyword.operator.comparison.php",settings:{foreground:"#08c0ef"}},{scope:["keyword.operator.heredoc.php","keyword.operator.nowdoc.php"],settings:{foreground:"#ff678d"}},{scope:"variable.other.class.php",settings:{foreground:"#ff6762"}},{scope:"invalid.illegal.non-null-typehinted.php",settings:{foreground:"#f44747"}},{scope:"variable.other.generic-type.haskell",settings:{foreground:"#ff678d"}},{scope:"storage.type.haskell",settings:{foreground:"#ffd452"}},{scope:"storage.type.cs",settings:{foreground:"#ffca00"}},{scope:"entity.name.variable.local.cs",settings:{foreground:"#ff6762"}},{scope:"entity.name.label.cs",settings:{foreground:"#ffca00"}},{scope:["entity.name.scope-resolution.function.call","entity.name.scope-resolution.function.definition"],settings:{foreground:"#ffca00"}},{scope:["punctuation.definition.delayed.unison","punctuation.definition.list.begin.unison","punctuation.definition.list.end.unison","punctuation.definition.ability.begin.unison","punctuation.definition.ability.end.unison","punctuation.operator.assignment.as.unison","punctuation.separator.pipe.unison","punctuation.separator.delimiter.unison","punctuation.definition.hash.unison"],settings:{foreground:"#ff6762"}},{scope:"support.constant.edge",settings:{foreground:"#ff678d"}},{scope:"support.type.prelude.elm",settings:{foreground:"#08c0ef"}},{scope:"support.constant.elm",settings:{foreground:"#ffd452"}},{scope:"entity.global.clojure",settings:{foreground:"#ffca00"}},{scope:"meta.symbol.clojure",settings:{foreground:"#ff6762"}},{scope:"constant.keyword.clojure",settings:{foreground:"#08c0ef"}},{scope:["meta.arguments.coffee","variable.parameter.function.coffee"],settings:{foreground:"#ff6762"}},{scope:"storage.modifier.import.groovy",settings:{foreground:"#ffca00"}},{scope:"meta.method.groovy",settings:{foreground:"#9d6afb"}},{scope:"meta.definition.variable.name.groovy",settings:{foreground:"#ff6762"}},{scope:"meta.definition.class.inherited.classes.groovy",settings:{foreground:"#5ecc71"}},{scope:"support.variable.semantic.hlsl",settings:{foreground:"#ffca00"}},{scope:["support.type.texture.hlsl","support.type.sampler.hlsl","support.type.object.hlsl","support.type.object.rw.hlsl","support.type.fx.hlsl","support.type.object.hlsl"],settings:{foreground:"#ff678d"}},{scope:["text.variable","text.bracketed"],settings:{foreground:"#ff6762"}},{scope:["support.type.swift","support.type.vb.asp"],settings:{foreground:"#ffca00"}},{scope:"meta.scope.prerequisites.makefile",settings:{foreground:"#ff6762"}},{scope:"source.makefile",settings:{foreground:"#ffca00"}},{scope:"source.ini",settings:{foreground:"#5ecc71"}},{scope:"constant.language.symbol.ruby",settings:{foreground:"#08c0ef"}},{scope:["function.parameter.ruby","function.parameter.cs"],settings:{foreground:"#79797F"}},{scope:"constant.language.symbol.elixir",settings:{foreground:"#08c0ef"}},{scope:"text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade",settings:{foreground:"#ff678d"}},{scope:"text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade",settings:{foreground:"#ff678d"}},{scope:"entity.name.function.xi",settings:{foreground:"#ffca00"}},{scope:"entity.name.class.xi",settings:{foreground:"#08c0ef"}},{scope:"constant.character.character-class.regexp.xi",settings:{foreground:"#ff6762"}},{scope:"constant.regexp.xi",settings:{foreground:"#ff678d"}},{scope:"keyword.control.xi",settings:{foreground:"#08c0ef"}},{scope:"invalid.xi",settings:{foreground:"#79797F"}},{scope:"beginning.punctuation.definition.quote.markdown.xi",settings:{foreground:"#5ecc71"}},{scope:"beginning.punctuation.definition.list.markdown.xi",settings:{foreground:"#84848A"}},{scope:"constant.character.xi",settings:{foreground:"#9d6afb"}},{scope:"accent.xi",settings:{foreground:"#9d6afb"}},{scope:"wikiword.xi",settings:{foreground:"#ffd452"}},{scope:"constant.other.color.rgb-value.xi",settings:{foreground:"#ffffff"}},{scope:"punctuation.definition.tag.xi",settings:{foreground:"#84848A"}},{scope:["support.constant.property-value.scss","support.constant.property-value.css"],settings:{foreground:"#ffd452"}},{scope:["keyword.operator.css","keyword.operator.scss","keyword.operator.less"],settings:{foreground:"#08c0ef"}},{scope:["support.constant.color.w3c-standard-color-name.css","support.constant.color.w3c-standard-color-name.scss"],settings:{foreground:"#ffd452"}},{scope:"punctuation.separator.list.comma.css",settings:{foreground:"#79797F"}},{scope:"support.type.vendored.property-name.css",settings:{foreground:"#08c0ef"}},{scope:"support.type.property-name.css",settings:{foreground:"#08c0ef"}},{scope:"support.type.property-name",settings:{foreground:"#79797F"}},{scope:"support.constant.property-value",settings:{foreground:"#79797F"}},{scope:"support.constant.font-name",settings:{foreground:"#ffd452"}},{scope:"entity.other.attribute-name.class.css",settings:{foreground:"#61d5c0",fontStyle:"normal"}},{scope:"entity.other.attribute-name.id",settings:{foreground:"#9d6afb",fontStyle:"normal"}},{scope:["entity.other.attribute-name.pseudo-element","entity.other.attribute-name.pseudo-class"],settings:{foreground:"#08c0ef"}},{scope:"meta.selector",settings:{foreground:"#ff678d"}},{scope:"selector.sass",settings:{foreground:"#ff6762"}},{scope:"rgb-value",settings:{foreground:"#08c0ef"}},{scope:"inline-color-decoration rgb-value",settings:{foreground:"#ffd452"}},{scope:"less rgb-value",settings:{foreground:"#ffd452"}},{scope:"control.elements",settings:{foreground:"#ffd452"}},{scope:"keyword.operator.less",settings:{foreground:"#ffd452"}},{scope:"entity.name.tag",settings:{foreground:"#ff6762"}},{scope:"entity.other.attribute-name",settings:{foreground:"#61d5c0",fontStyle:"normal"}},{scope:"constant.character.entity",settings:{foreground:"#ff6762"}},{scope:"meta.tag",settings:{foreground:"#79797F"}},{scope:"invalid.illegal.bad-ampersand.html",settings:{foreground:"#79797F"}},{scope:"markup.heading",settings:{foreground:"#ff6762"}},{scope:["markup.heading punctuation.definition.heading","entity.name.section"],settings:{foreground:"#9d6afb"}},{scope:"entity.name.section.markdown",settings:{foreground:"#ff6762"}},{scope:"punctuation.definition.heading.markdown",settings:{foreground:"#ff6762"}},{scope:"markup.heading.setext",settings:{foreground:"#79797F"}},{scope:["markup.heading.setext.1.markdown","markup.heading.setext.2.markdown"],settings:{foreground:"#ff6762"}},{scope:["markup.bold","todo.bold"],settings:{foreground:"#ffd452"}},{scope:"punctuation.definition.bold",settings:{foreground:"#ffca00"}},{scope:"punctuation.definition.bold.markdown",settings:{foreground:"#ffd452"}},{scope:["markup.italic","punctuation.definition.italic","todo.emphasis"],settings:{foreground:"#ff678d",fontStyle:"italic"}},{scope:"emphasis md",settings:{foreground:"#ff678d"}},{scope:"markup.italic.markdown",settings:{fontStyle:"italic"}},{scope:["markup.underline.link.markdown","markup.underline.link.image.markdown"],settings:{foreground:"#ff678d"}},{scope:["string.other.link.title.markdown","string.other.link.description.markdown"],settings:{foreground:"#9d6afb"}},{scope:"punctuation.definition.metadata.markdown",settings:{foreground:"#ff6762"}},{scope:["markup.inline.raw.markdown","markup.inline.raw.string.markdown"],settings:{foreground:"#5ecc71"}},{scope:"punctuation.definition.list.begin.markdown",settings:{foreground:"#ff6762"}},{scope:"punctuation.definition.list.markdown",settings:{foreground:"#ff6762"}},{scope:"beginning.punctuation.definition.list.markdown",settings:{foreground:"#ff6762"}},{scope:["punctuation.definition.string.begin.markdown","punctuation.definition.string.end.markdown"],settings:{foreground:"#ff6762"}},{scope:"markup.quote.markdown",settings:{foreground:"#84848A"}},{scope:"keyword.other.unit",settings:{foreground:"#ff6762"}},{scope:"markup.changed.diff",settings:{foreground:"#ffca00"}},{scope:["meta.diff.header.from-file","meta.diff.header.to-file","punctuation.definition.from-file.diff","punctuation.definition.to-file.diff"],settings:{foreground:"#9d6afb"}},{scope:"markup.inserted.diff",settings:{foreground:"#5ecc71"}},{scope:"markup.deleted.diff",settings:{foreground:"#ff6762"}},{scope:"string.regexp",settings:{foreground:"#64d1db"}},{scope:"constant.other.character-class.regexp",settings:{foreground:"#ff6762"}},{scope:"keyword.operator.quantifier.regexp",settings:{foreground:"#ffd452"}},{scope:"constant.character.escape",settings:{foreground:"#68cdf2"}},{scope:"source.json meta.structure.dictionary.json > string.quoted.json",settings:{foreground:"#ff6762"}},{scope:"source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string",settings:{foreground:"#ff6762"}},{scope:["source.json meta.structure.dictionary.json > value.json > string.quoted.json","source.json meta.structure.array.json > value.json > string.quoted.json","source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation","source.json meta.structure.array.json > value.json > string.quoted.json > punctuation"],settings:{foreground:"#5ecc71"}},{scope:["source.json meta.structure.dictionary.json > constant.language.json","source.json meta.structure.array.json > constant.language.json"],settings:{foreground:"#08c0ef"}},{scope:"support.type.property-name.json",settings:{foreground:"#ff6762"}},{scope:"support.type.property-name.json punctuation",settings:{foreground:"#ff6762"}},{scope:"punctuation.definition.block.sequence.item.yaml",settings:{foreground:"#79797F"}},{scope:"block.scope.end",settings:{foreground:"#79797F"}},{scope:"block.scope.begin",settings:{foreground:"#79797F"}},{scope:"token.info-token",settings:{foreground:"#9d6afb"}},{scope:"token.warn-token",settings:{foreground:"#ffd452"}},{scope:"token.error-token",settings:{foreground:"#f44747"}},{scope:"token.debug-token",settings:{foreground:"#ff678d"}},{scope:"invalid.illegal",settings:{foreground:"#ffffff"}},{scope:"invalid.broken",settings:{foreground:"#ffffff"}},{scope:"invalid.deprecated",settings:{foreground:"#ffffff"}},{scope:"invalid.unimplemented",settings:{foreground:"#ffffff"}}],w={comment:"#84848A",string:"#5ecc71",number:"#68cdf2",regexp:"#64d1db",keyword:"#ff678d",variable:"#ffa359",parameter:"#adadb1",property:"#ffa359",function:"#9d6afb",method:"#9d6afb",type:"#d568ea",class:"#d568ea",namespace:"#ffca00",enumMember:"#08c0ef","variable.constant":"#ffd452","variable.defaultLibrary":"#ffca00"},x={name:b,type:h,colors:q,tokenColors:v,semanticTokenColors:w};export{h as type,v as tokenColors,w as semanticTokenColors,b as name,x as default,q as colors}; diff --git a/lib/crates/fabro-spa/assets/assets/chunk-tqzz87j8.js b/lib/crates/fabro-spa/assets/assets/chunk-tqzz87j8.js new file mode 100644 index 000000000..b56d27055 --- /dev/null +++ b/lib/crates/fabro-spa/assets/assets/chunk-tqzz87j8.js @@ -0,0 +1 @@ +import"./chunk-q07bg6gn.js";var b="pierre-light",q="light",v={"editor.background":"#ffffff","editor.foreground":"#070707",foreground:"#070707",focusBorder:"#009fff","selection.background":"#dfebff","editor.selectionBackground":"#009fff2e","editor.lineHighlightBackground":"#dfebff8c","editorCursor.foreground":"#009fff","editorLineNumber.foreground":"#84848A","editorLineNumber.activeForeground":"#6C6C71","editorIndentGuide.background":"#eeeeef","editorIndentGuide.activeBackground":"#dbdbdd","diffEditor.insertedTextBackground":"#00cab133","diffEditor.deletedTextBackground":"#ff2e3f33","sideBar.background":"#f8f8f8","sideBar.foreground":"#6C6C71","sideBar.border":"#eeeeef","sideBarTitle.foreground":"#070707","sideBarSectionHeader.background":"#f8f8f8","sideBarSectionHeader.foreground":"#6C6C71","sideBarSectionHeader.border":"#eeeeef","activityBar.background":"#f8f8f8","activityBar.foreground":"#070707","activityBar.border":"#eeeeef","activityBar.activeBorder":"#009fff","activityBarBadge.background":"#009fff","activityBarBadge.foreground":"#ffffff","titleBar.activeBackground":"#f8f8f8","titleBar.activeForeground":"#070707","titleBar.inactiveBackground":"#f8f8f8","titleBar.inactiveForeground":"#84848A","titleBar.border":"#eeeeef","list.activeSelectionBackground":"#dfebffcc","list.activeSelectionForeground":"#070707","list.inactiveSelectionBackground":"#dfebff73","list.hoverBackground":"#dfebff59","list.focusOutline":"#009fff","tab.activeBackground":"#ffffff","tab.activeForeground":"#070707","tab.activeBorderTop":"#009fff","tab.inactiveBackground":"#f8f8f8","tab.inactiveForeground":"#84848A","tab.border":"#eeeeef","editorGroupHeader.tabsBackground":"#f8f8f8","editorGroupHeader.tabsBorder":"#eeeeef","panel.background":"#f8f8f8","panel.border":"#eeeeef","panelTitle.activeBorder":"#009fff","panelTitle.activeForeground":"#070707","panelTitle.inactiveForeground":"#84848A","statusBar.background":"#f8f8f8","statusBar.foreground":"#6C6C71","statusBar.border":"#eeeeef","statusBar.noFolderBackground":"#f8f8f8","statusBar.debuggingBackground":"#ffca00","statusBar.debuggingForeground":"#ffffff","statusBarItem.remoteBackground":"#f8f8f8","statusBarItem.remoteForeground":"#6C6C71","input.background":"#f2f2f3","input.border":"#dbdbdd","input.foreground":"#070707","input.placeholderForeground":"#8E8E95","dropdown.background":"#f2f2f3","dropdown.border":"#dbdbdd","dropdown.foreground":"#070707","button.background":"#009fff","button.foreground":"#ffffff","button.hoverBackground":"#1aa9ff","textLink.foreground":"#009fff","textLink.activeForeground":"#009fff","gitDecoration.addedResourceForeground":"#00cab1","gitDecoration.conflictingResourceForeground":"#ffca00","gitDecoration.modifiedResourceForeground":"#009fff","gitDecoration.deletedResourceForeground":"#ff2e3f","gitDecoration.untrackedResourceForeground":"#00cab1","gitDecoration.ignoredResourceForeground":"#84848A","terminal.titleForeground":"#6C6C71","terminal.titleInactiveForeground":"#84848A","terminal.background":"#f8f8f8","terminal.foreground":"#6C6C71","terminal.ansiBlack":"#1F1F21","terminal.ansiRed":"#ff2e3f","terminal.ansiGreen":"#0dbe4e","terminal.ansiYellow":"#ffca00","terminal.ansiBlue":"#009fff","terminal.ansiMagenta":"#c635e4","terminal.ansiCyan":"#08c0ef","terminal.ansiWhite":"#c6c6c8","terminal.ansiBrightBlack":"#1F1F21","terminal.ansiBrightRed":"#ff2e3f","terminal.ansiBrightGreen":"#0dbe4e","terminal.ansiBrightYellow":"#ffca00","terminal.ansiBrightBlue":"#009fff","terminal.ansiBrightMagenta":"#c635e4","terminal.ansiBrightCyan":"#08c0ef","terminal.ansiBrightWhite":"#c6c6c8"},w=[{scope:["comment","punctuation.definition.comment"],settings:{foreground:"#84848A"}},{scope:"comment markup.link",settings:{foreground:"#84848A"}},{scope:["string","constant.other.symbol"],settings:{foreground:"#199f43"}},{scope:["punctuation.definition.string.begin","punctuation.definition.string.end"],settings:{foreground:"#199f43"}},{scope:["constant.numeric","constant.language.boolean"],settings:{foreground:"#1ca1c7"}},{scope:"constant",settings:{foreground:"#d5a910"}},{scope:"punctuation.definition.constant",settings:{foreground:"#d5a910"}},{scope:"constant.language",settings:{foreground:"#1ca1c7"}},{scope:"variable.other.constant",settings:{foreground:"#d5a910"}},{scope:"keyword",settings:{foreground:"#fc2b73"}},{scope:"keyword.control",settings:{foreground:"#fc2b73"}},{scope:["storage","storage.type","storage.modifier"],settings:{foreground:"#fc2b73"}},{scope:"token.storage",settings:{foreground:"#fc2b73"}},{scope:["keyword.operator.new","keyword.operator.expression.instanceof","keyword.operator.expression.typeof","keyword.operator.expression.void","keyword.operator.expression.delete","keyword.operator.expression.in","keyword.operator.expression.of","keyword.operator.expression.keyof"],settings:{foreground:"#fc2b73"}},{scope:"keyword.operator.delete",settings:{foreground:"#fc2b73"}},{scope:["variable","identifier","meta.definition.variable"],settings:{foreground:"#d47628"}},{scope:["variable.other.readwrite","meta.object-literal.key","support.variable.property","support.variable.object.process","support.variable.object.node"],settings:{foreground:"#d47628"}},{scope:"variable.language",settings:{foreground:"#d5a910"}},{scope:"variable.parameter.function",settings:{foreground:"#79797F"}},{scope:"function.parameter",settings:{foreground:"#79797F"}},{scope:"variable.parameter",settings:{foreground:"#79797F"}},{scope:"variable.parameter.function.language.python",settings:{foreground:"#d5a910"}},{scope:"variable.parameter.function.python",settings:{foreground:"#d5a910"}},{scope:["support.function","entity.name.function","meta.function-call","meta.require","support.function.any-method","variable.function"],settings:{foreground:"#7b43f8"}},{scope:"keyword.other.special-method",settings:{foreground:"#7b43f8"}},{scope:"entity.name.function",settings:{foreground:"#7b43f8"}},{scope:"support.function.console",settings:{foreground:"#7b43f8"}},{scope:["support.type","entity.name.type","entity.name.class","storage.type"],settings:{foreground:"#c635e4"}},{scope:["support.class","entity.name.type.class"],settings:{foreground:"#c635e4"}},{scope:["entity.name.class","variable.other.class.js","variable.other.class.ts"],settings:{foreground:"#c635e4"}},{scope:"entity.name.class.identifier.namespace.type",settings:{foreground:"#c635e4"}},{scope:"entity.name.type.namespace",settings:{foreground:"#d5a910"}},{scope:"entity.other.inherited-class",settings:{foreground:"#c635e4"}},{scope:"entity.name.namespace",settings:{foreground:"#d5a910"}},{scope:"keyword.operator",settings:{foreground:"#79797F"}},{scope:["keyword.operator.logical","keyword.operator.bitwise","keyword.operator.channel"],settings:{foreground:"#08c0ef"}},{scope:["keyword.operator.arithmetic","keyword.operator.comparison","keyword.operator.relational","keyword.operator.increment","keyword.operator.decrement"],settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.assignment",settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.assignment.compound",settings:{foreground:"#fc2b73"}},{scope:["keyword.operator.assignment.compound.js","keyword.operator.assignment.compound.ts"],settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.ternary",settings:{foreground:"#fc2b73"}},{scope:"keyword.operator.optional",settings:{foreground:"#fc2b73"}},{scope:"punctuation",settings:{foreground:"#79797F"}},{scope:"punctuation.separator.delimiter",settings:{foreground:"#79797F"}},{scope:"punctuation.separator.key-value",settings:{foreground:"#79797F"}},{scope:"punctuation.terminator",settings:{foreground:"#79797F"}},{scope:"meta.brace",settings:{foreground:"#79797F"}},{scope:"meta.brace.square",settings:{foreground:"#79797F"}},{scope:"meta.brace.round",settings:{foreground:"#79797F"}},{scope:"function.brace",settings:{foreground:"#79797F"}},{scope:["punctuation.definition.parameters","punctuation.definition.typeparameters"],settings:{foreground:"#79797F"}},{scope:["punctuation.definition.block","punctuation.definition.tag"],settings:{foreground:"#79797F"}},{scope:["meta.tag.tsx","meta.tag.jsx","meta.tag.js","meta.tag.ts"],settings:{foreground:"#79797F"}},{scope:"keyword.operator.expression.import",settings:{foreground:"#7b43f8"}},{scope:"keyword.operator.module",settings:{foreground:"#fc2b73"}},{scope:"support.type.object.console",settings:{foreground:"#d47628"}},{scope:["support.module.node","support.type.object.module","entity.name.type.module"],settings:{foreground:"#d5a910"}},{scope:"support.constant.math",settings:{foreground:"#d5a910"}},{scope:"support.constant.property.math",settings:{foreground:"#d5a910"}},{scope:"support.constant.json",settings:{foreground:"#d5a910"}},{scope:"support.type.object.dom",settings:{foreground:"#08c0ef"}},{scope:["support.variable.dom","support.variable.property.dom"],settings:{foreground:"#d47628"}},{scope:"support.variable.property.process",settings:{foreground:"#d5a910"}},{scope:"meta.property.object",settings:{foreground:"#d47628"}},{scope:"variable.parameter.function.js",settings:{foreground:"#d47628"}},{scope:["keyword.other.template.begin","keyword.other.template.end"],settings:{foreground:"#199f43"}},{scope:["keyword.other.substitution.begin","keyword.other.substitution.end"],settings:{foreground:"#199f43"}},{scope:["punctuation.definition.template-expression.begin","punctuation.definition.template-expression.end"],settings:{foreground:"#fc2b73"}},{scope:"meta.template.expression",settings:{foreground:"#79797F"}},{scope:"punctuation.section.embedded",settings:{foreground:"#d47628"}},{scope:"variable.interpolation",settings:{foreground:"#d47628"}},{scope:["punctuation.section.embedded.begin","punctuation.section.embedded.end"],settings:{foreground:"#fc2b73"}},{scope:"punctuation.quasi.element",settings:{foreground:"#fc2b73"}},{scope:["support.type.primitive.ts","support.type.builtin.ts","support.type.primitive.tsx","support.type.builtin.tsx"],settings:{foreground:"#c635e4"}},{scope:"support.type.type.flowtype",settings:{foreground:"#7b43f8"}},{scope:"support.type.primitive",settings:{foreground:"#c635e4"}},{scope:"support.variable.magic.python",settings:{foreground:"#d52c36"}},{scope:"variable.parameter.function.language.special.self.python",settings:{foreground:"#d5a910"}},{scope:["punctuation.separator.period.python","punctuation.separator.element.python","punctuation.parenthesis.begin.python","punctuation.parenthesis.end.python"],settings:{foreground:"#79797F"}},{scope:["punctuation.definition.arguments.begin.python","punctuation.definition.arguments.end.python","punctuation.separator.arguments.python","punctuation.definition.list.begin.python","punctuation.definition.list.end.python"],settings:{foreground:"#79797F"}},{scope:"support.type.python",settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.logical.python",settings:{foreground:"#fc2b73"}},{scope:"meta.function-call.generic.python",settings:{foreground:"#7b43f8"}},{scope:"constant.character.format.placeholder.other.python",settings:{foreground:"#d5a910"}},{scope:"meta.function.decorator.python",settings:{foreground:"#7b43f8"}},{scope:["support.token.decorator.python","meta.function.decorator.identifier.python"],settings:{foreground:"#08c0ef"}},{scope:"storage.modifier.lifetime.rust",settings:{foreground:"#79797F"}},{scope:"support.function.std.rust",settings:{foreground:"#7b43f8"}},{scope:"entity.name.lifetime.rust",settings:{foreground:"#d5a910"}},{scope:"variable.language.rust",settings:{foreground:"#d52c36"}},{scope:"keyword.operator.misc.rust",settings:{foreground:"#79797F"}},{scope:"keyword.operator.sigil.rust",settings:{foreground:"#fc2b73"}},{scope:"support.constant.core.rust",settings:{foreground:"#d5a910"}},{scope:["meta.function.c","meta.function.cpp"],settings:{foreground:"#d52c36"}},{scope:["punctuation.section.block.begin.bracket.curly.cpp","punctuation.section.block.end.bracket.curly.cpp","punctuation.terminator.statement.c","punctuation.section.block.begin.bracket.curly.c","punctuation.section.block.end.bracket.curly.c","punctuation.section.parens.begin.bracket.round.c","punctuation.section.parens.end.bracket.round.c","punctuation.section.parameters.begin.bracket.round.c","punctuation.section.parameters.end.bracket.round.c"],settings:{foreground:"#79797F"}},{scope:["keyword.operator.assignment.c","keyword.operator.comparison.c","keyword.operator.c","keyword.operator.increment.c","keyword.operator.decrement.c","keyword.operator.bitwise.shift.c"],settings:{foreground:"#fc2b73"}},{scope:["keyword.operator.assignment.cpp","keyword.operator.comparison.cpp","keyword.operator.cpp","keyword.operator.increment.cpp","keyword.operator.decrement.cpp","keyword.operator.bitwise.shift.cpp"],settings:{foreground:"#fc2b73"}},{scope:["punctuation.separator.c","punctuation.separator.cpp"],settings:{foreground:"#fc2b73"}},{scope:["support.type.posix-reserved.c","support.type.posix-reserved.cpp"],settings:{foreground:"#08c0ef"}},{scope:["keyword.operator.sizeof.c","keyword.operator.sizeof.cpp"],settings:{foreground:"#fc2b73"}},{scope:"variable.c",settings:{foreground:"#79797F"}},{scope:["storage.type.annotation.java","storage.type.object.array.java"],settings:{foreground:"#d5a910"}},{scope:"source.java",settings:{foreground:"#d52c36"}},{scope:["punctuation.section.block.begin.java","punctuation.section.block.end.java","punctuation.definition.method-parameters.begin.java","punctuation.definition.method-parameters.end.java","meta.method.identifier.java","punctuation.section.method.begin.java","punctuation.section.method.end.java","punctuation.terminator.java","punctuation.section.class.begin.java","punctuation.section.class.end.java","punctuation.section.inner-class.begin.java","punctuation.section.inner-class.end.java","meta.method-call.java","punctuation.section.class.begin.bracket.curly.java","punctuation.section.class.end.bracket.curly.java","punctuation.section.method.begin.bracket.curly.java","punctuation.section.method.end.bracket.curly.java","punctuation.separator.period.java","punctuation.bracket.angle.java","punctuation.definition.annotation.java","meta.method.body.java"],settings:{foreground:"#79797F"}},{scope:"meta.method.java",settings:{foreground:"#7b43f8"}},{scope:["storage.modifier.import.java","storage.type.java","storage.type.generic.java"],settings:{foreground:"#d5a910"}},{scope:"keyword.operator.instanceof.java",settings:{foreground:"#fc2b73"}},{scope:"meta.definition.variable.name.java",settings:{foreground:"#d52c36"}},{scope:"token.variable.parameter.java",settings:{foreground:"#79797F"}},{scope:"import.storage.java",settings:{foreground:"#d5a910"}},{scope:"token.package.keyword",settings:{foreground:"#fc2b73"}},{scope:"token.package",settings:{foreground:"#79797F"}},{scope:"token.storage.type.java",settings:{foreground:"#d5a910"}},{scope:"keyword.operator.assignment.go",settings:{foreground:"#d5a910"}},{scope:["keyword.operator.arithmetic.go","keyword.operator.address.go"],settings:{foreground:"#fc2b73"}},{scope:"entity.name.package.go",settings:{foreground:"#d5a910"}},{scope:["support.other.namespace.use.php","support.other.namespace.use-as.php","support.other.namespace.php","entity.other.alias.php","meta.interface.php"],settings:{foreground:"#d5a910"}},{scope:"keyword.operator.error-control.php",settings:{foreground:"#fc2b73"}},{scope:"keyword.operator.type.php",settings:{foreground:"#fc2b73"}},{scope:["punctuation.section.array.begin.php","punctuation.section.array.end.php"],settings:{foreground:"#79797F"}},{scope:["storage.type.php","meta.other.type.phpdoc.php","keyword.other.type.php","keyword.other.array.phpdoc.php"],settings:{foreground:"#d5a910"}},{scope:["meta.function-call.php","meta.function-call.object.php","meta.function-call.static.php"],settings:{foreground:"#7b43f8"}},{scope:["punctuation.definition.parameters.begin.bracket.round.php","punctuation.definition.parameters.end.bracket.round.php","punctuation.separator.delimiter.php","punctuation.section.scope.begin.php","punctuation.section.scope.end.php","punctuation.terminator.expression.php","punctuation.definition.arguments.begin.bracket.round.php","punctuation.definition.arguments.end.bracket.round.php","punctuation.definition.storage-type.begin.bracket.round.php","punctuation.definition.storage-type.end.bracket.round.php","punctuation.definition.array.begin.bracket.round.php","punctuation.definition.array.end.bracket.round.php","punctuation.definition.begin.bracket.round.php","punctuation.definition.end.bracket.round.php","punctuation.definition.begin.bracket.curly.php","punctuation.definition.end.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php","punctuation.definition.section.switch-block.start.bracket.curly.php","punctuation.definition.section.switch-block.begin.bracket.curly.php","punctuation.definition.section.switch-block.end.bracket.curly.php"],settings:{foreground:"#79797F"}},{scope:["support.constant.ext.php","support.constant.std.php","support.constant.core.php","support.constant.parser-token.php"],settings:{foreground:"#d5a910"}},{scope:["entity.name.goto-label.php","support.other.php"],settings:{foreground:"#7b43f8"}},{scope:["keyword.operator.logical.php","keyword.operator.bitwise.php","keyword.operator.arithmetic.php"],settings:{foreground:"#08c0ef"}},{scope:"keyword.operator.regexp.php",settings:{foreground:"#fc2b73"}},{scope:"keyword.operator.comparison.php",settings:{foreground:"#08c0ef"}},{scope:["keyword.operator.heredoc.php","keyword.operator.nowdoc.php"],settings:{foreground:"#fc2b73"}},{scope:"variable.other.class.php",settings:{foreground:"#d52c36"}},{scope:"invalid.illegal.non-null-typehinted.php",settings:{foreground:"#f44747"}},{scope:"variable.other.generic-type.haskell",settings:{foreground:"#fc2b73"}},{scope:"storage.type.haskell",settings:{foreground:"#d5a910"}},{scope:"storage.type.cs",settings:{foreground:"#d5a910"}},{scope:"entity.name.variable.local.cs",settings:{foreground:"#d52c36"}},{scope:"entity.name.label.cs",settings:{foreground:"#d5a910"}},{scope:["entity.name.scope-resolution.function.call","entity.name.scope-resolution.function.definition"],settings:{foreground:"#d5a910"}},{scope:["punctuation.definition.delayed.unison","punctuation.definition.list.begin.unison","punctuation.definition.list.end.unison","punctuation.definition.ability.begin.unison","punctuation.definition.ability.end.unison","punctuation.operator.assignment.as.unison","punctuation.separator.pipe.unison","punctuation.separator.delimiter.unison","punctuation.definition.hash.unison"],settings:{foreground:"#d52c36"}},{scope:"support.constant.edge",settings:{foreground:"#fc2b73"}},{scope:"support.type.prelude.elm",settings:{foreground:"#08c0ef"}},{scope:"support.constant.elm",settings:{foreground:"#d5a910"}},{scope:"entity.global.clojure",settings:{foreground:"#d5a910"}},{scope:"meta.symbol.clojure",settings:{foreground:"#d52c36"}},{scope:"constant.keyword.clojure",settings:{foreground:"#08c0ef"}},{scope:["meta.arguments.coffee","variable.parameter.function.coffee"],settings:{foreground:"#d52c36"}},{scope:"storage.modifier.import.groovy",settings:{foreground:"#d5a910"}},{scope:"meta.method.groovy",settings:{foreground:"#7b43f8"}},{scope:"meta.definition.variable.name.groovy",settings:{foreground:"#d52c36"}},{scope:"meta.definition.class.inherited.classes.groovy",settings:{foreground:"#199f43"}},{scope:"support.variable.semantic.hlsl",settings:{foreground:"#d5a910"}},{scope:["support.type.texture.hlsl","support.type.sampler.hlsl","support.type.object.hlsl","support.type.object.rw.hlsl","support.type.fx.hlsl","support.type.object.hlsl"],settings:{foreground:"#fc2b73"}},{scope:["text.variable","text.bracketed"],settings:{foreground:"#d52c36"}},{scope:["support.type.swift","support.type.vb.asp"],settings:{foreground:"#d5a910"}},{scope:"meta.scope.prerequisites.makefile",settings:{foreground:"#d52c36"}},{scope:"source.makefile",settings:{foreground:"#d5a910"}},{scope:"source.ini",settings:{foreground:"#199f43"}},{scope:"constant.language.symbol.ruby",settings:{foreground:"#08c0ef"}},{scope:["function.parameter.ruby","function.parameter.cs"],settings:{foreground:"#79797F"}},{scope:"constant.language.symbol.elixir",settings:{foreground:"#08c0ef"}},{scope:"text.html.laravel-blade source.php.embedded.line.html entity.name.tag.laravel-blade",settings:{foreground:"#fc2b73"}},{scope:"text.html.laravel-blade source.php.embedded.line.html support.constant.laravel-blade",settings:{foreground:"#fc2b73"}},{scope:"entity.name.function.xi",settings:{foreground:"#d5a910"}},{scope:"entity.name.class.xi",settings:{foreground:"#08c0ef"}},{scope:"constant.character.character-class.regexp.xi",settings:{foreground:"#d52c36"}},{scope:"constant.regexp.xi",settings:{foreground:"#fc2b73"}},{scope:"keyword.control.xi",settings:{foreground:"#08c0ef"}},{scope:"invalid.xi",settings:{foreground:"#79797F"}},{scope:"beginning.punctuation.definition.quote.markdown.xi",settings:{foreground:"#199f43"}},{scope:"beginning.punctuation.definition.list.markdown.xi",settings:{foreground:"#84848A"}},{scope:"constant.character.xi",settings:{foreground:"#7b43f8"}},{scope:"accent.xi",settings:{foreground:"#7b43f8"}},{scope:"wikiword.xi",settings:{foreground:"#d5a910"}},{scope:"constant.other.color.rgb-value.xi",settings:{foreground:"#ffffff"}},{scope:"punctuation.definition.tag.xi",settings:{foreground:"#84848A"}},{scope:["support.constant.property-value.scss","support.constant.property-value.css"],settings:{foreground:"#d5a910"}},{scope:["keyword.operator.css","keyword.operator.scss","keyword.operator.less"],settings:{foreground:"#08c0ef"}},{scope:["support.constant.color.w3c-standard-color-name.css","support.constant.color.w3c-standard-color-name.scss"],settings:{foreground:"#d5a910"}},{scope:"punctuation.separator.list.comma.css",settings:{foreground:"#79797F"}},{scope:"support.type.vendored.property-name.css",settings:{foreground:"#08c0ef"}},{scope:"support.type.property-name.css",settings:{foreground:"#08c0ef"}},{scope:"support.type.property-name",settings:{foreground:"#79797F"}},{scope:"support.constant.property-value",settings:{foreground:"#79797F"}},{scope:"support.constant.font-name",settings:{foreground:"#d5a910"}},{scope:"entity.other.attribute-name.class.css",settings:{foreground:"#16a994",fontStyle:"normal"}},{scope:"entity.other.attribute-name.id",settings:{foreground:"#7b43f8",fontStyle:"normal"}},{scope:["entity.other.attribute-name.pseudo-element","entity.other.attribute-name.pseudo-class"],settings:{foreground:"#08c0ef"}},{scope:"meta.selector",settings:{foreground:"#fc2b73"}},{scope:"selector.sass",settings:{foreground:"#d52c36"}},{scope:"rgb-value",settings:{foreground:"#08c0ef"}},{scope:"inline-color-decoration rgb-value",settings:{foreground:"#d5a910"}},{scope:"less rgb-value",settings:{foreground:"#d5a910"}},{scope:"control.elements",settings:{foreground:"#d5a910"}},{scope:"keyword.operator.less",settings:{foreground:"#d5a910"}},{scope:"entity.name.tag",settings:{foreground:"#d52c36"}},{scope:"entity.other.attribute-name",settings:{foreground:"#16a994",fontStyle:"normal"}},{scope:"constant.character.entity",settings:{foreground:"#d52c36"}},{scope:"meta.tag",settings:{foreground:"#79797F"}},{scope:"invalid.illegal.bad-ampersand.html",settings:{foreground:"#79797F"}},{scope:"markup.heading",settings:{foreground:"#d52c36"}},{scope:["markup.heading punctuation.definition.heading","entity.name.section"],settings:{foreground:"#7b43f8"}},{scope:"entity.name.section.markdown",settings:{foreground:"#d52c36"}},{scope:"punctuation.definition.heading.markdown",settings:{foreground:"#d52c36"}},{scope:"markup.heading.setext",settings:{foreground:"#79797F"}},{scope:["markup.heading.setext.1.markdown","markup.heading.setext.2.markdown"],settings:{foreground:"#d52c36"}},{scope:["markup.bold","todo.bold"],settings:{foreground:"#d5a910"}},{scope:"punctuation.definition.bold",settings:{foreground:"#d5a910"}},{scope:"punctuation.definition.bold.markdown",settings:{foreground:"#d5a910"}},{scope:["markup.italic","punctuation.definition.italic","todo.emphasis"],settings:{foreground:"#fc2b73",fontStyle:"italic"}},{scope:"emphasis md",settings:{foreground:"#fc2b73"}},{scope:"markup.italic.markdown",settings:{fontStyle:"italic"}},{scope:["markup.underline.link.markdown","markup.underline.link.image.markdown"],settings:{foreground:"#fc2b73"}},{scope:["string.other.link.title.markdown","string.other.link.description.markdown"],settings:{foreground:"#7b43f8"}},{scope:"punctuation.definition.metadata.markdown",settings:{foreground:"#d52c36"}},{scope:["markup.inline.raw.markdown","markup.inline.raw.string.markdown"],settings:{foreground:"#199f43"}},{scope:"punctuation.definition.list.begin.markdown",settings:{foreground:"#d52c36"}},{scope:"punctuation.definition.list.markdown",settings:{foreground:"#d52c36"}},{scope:"beginning.punctuation.definition.list.markdown",settings:{foreground:"#d52c36"}},{scope:["punctuation.definition.string.begin.markdown","punctuation.definition.string.end.markdown"],settings:{foreground:"#d52c36"}},{scope:"markup.quote.markdown",settings:{foreground:"#84848A"}},{scope:"keyword.other.unit",settings:{foreground:"#d52c36"}},{scope:"markup.changed.diff",settings:{foreground:"#d5a910"}},{scope:["meta.diff.header.from-file","meta.diff.header.to-file","punctuation.definition.from-file.diff","punctuation.definition.to-file.diff"],settings:{foreground:"#7b43f8"}},{scope:"markup.inserted.diff",settings:{foreground:"#199f43"}},{scope:"markup.deleted.diff",settings:{foreground:"#d52c36"}},{scope:"string.regexp",settings:{foreground:"#17a5af"}},{scope:"constant.other.character-class.regexp",settings:{foreground:"#d52c36"}},{scope:"keyword.operator.quantifier.regexp",settings:{foreground:"#d5a910"}},{scope:"constant.character.escape",settings:{foreground:"#1ca1c7"}},{scope:"source.json meta.structure.dictionary.json > string.quoted.json",settings:{foreground:"#d52c36"}},{scope:"source.json meta.structure.dictionary.json > string.quoted.json > punctuation.string",settings:{foreground:"#d52c36"}},{scope:["source.json meta.structure.dictionary.json > value.json > string.quoted.json","source.json meta.structure.array.json > value.json > string.quoted.json","source.json meta.structure.dictionary.json > value.json > string.quoted.json > punctuation","source.json meta.structure.array.json > value.json > string.quoted.json > punctuation"],settings:{foreground:"#199f43"}},{scope:["source.json meta.structure.dictionary.json > constant.language.json","source.json meta.structure.array.json > constant.language.json"],settings:{foreground:"#08c0ef"}},{scope:"support.type.property-name.json",settings:{foreground:"#d52c36"}},{scope:"support.type.property-name.json punctuation",settings:{foreground:"#d52c36"}},{scope:"punctuation.definition.block.sequence.item.yaml",settings:{foreground:"#79797F"}},{scope:"block.scope.end",settings:{foreground:"#79797F"}},{scope:"block.scope.begin",settings:{foreground:"#79797F"}},{scope:"token.info-token",settings:{foreground:"#7b43f8"}},{scope:"token.warn-token",settings:{foreground:"#d5a910"}},{scope:"token.error-token",settings:{foreground:"#f44747"}},{scope:"token.debug-token",settings:{foreground:"#fc2b73"}},{scope:"invalid.illegal",settings:{foreground:"#ffffff"}},{scope:"invalid.broken",settings:{foreground:"#ffffff"}},{scope:"invalid.deprecated",settings:{foreground:"#ffffff"}},{scope:"invalid.unimplemented",settings:{foreground:"#ffffff"}}],x={comment:"#84848A",string:"#199f43",number:"#1ca1c7",regexp:"#17a5af",keyword:"#fc2b73",variable:"#d47628",parameter:"#79797F",property:"#d47628",function:"#7b43f8",method:"#7b43f8",type:"#c635e4",class:"#c635e4",namespace:"#d5a910",enumMember:"#08c0ef","variable.constant":"#d5a910","variable.defaultLibrary":"#d5a910"},z={name:b,type:q,colors:v,tokenColors:w,semanticTokenColors:x};export{q as type,w as tokenColors,x as semanticTokenColors,b as name,z as default,v as colors}; diff --git a/lib/crates/fabro-spa/assets/assets/entry-1addt5fg.js b/lib/crates/fabro-spa/assets/assets/entry-1addt5fg.js deleted file mode 100644 index 06852a53f..000000000 --- a/lib/crates/fabro-spa/assets/assets/entry-1addt5fg.js +++ /dev/null @@ -1,2402 +0,0 @@ -import{X as h,Y as D3,Z as p5,_ as x}from"./chunk-q07bg6gn.js";var J0=D3((va,YU)=>{(function(){function Z(j,Y0){Object.defineProperty(z.prototype,j,{get:function(){console.warn("%s(...) is deprecated in plain JavaScript React classes. %s",Y0[0],Y0[1])}})}function Y(j){if(j===null||typeof j!=="object")return null;return j=B1&&j[B1]||j["@@iterator"],typeof j==="function"?j:null}function Q(j,Y0){j=(j=j.constructor)&&(j.displayName||j.name)||"ReactClass";var A0=j+"."+Y0;R0[A0]||(console.error("Can't call %s on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the %s component.",Y0,j),R0[A0]=!0)}function z(j,Y0,A0){this.props=j,this.context=Y0,this.refs=n5,this.updater=A0||C1}function q(){}function B(j,Y0,A0){this.props=j,this.context=Y0,this.refs=n5,this.updater=A0||C1}function W(){}function $(j){return""+j}function U(j){try{$(j);var Y0=!1}catch(k0){Y0=!0}if(Y0){Y0=console;var A0=Y0.error,T0=typeof Symbol==="function"&&Symbol.toStringTag&&j[Symbol.toStringTag]||j.constructor.name||"Object";return A0.call(Y0,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",T0),$(j)}}function M(j){if(j==null)return null;if(typeof j==="function")return j.$$typeof===t6?null:j.displayName||j.name||null;if(typeof j==="string")return j;switch(j){case K0:return"Fragment";case z0:return"Profiler";case f:return"StrictMode";case x0:return"Suspense";case H0:return"SuspenseList";case R1:return"Activity"}if(typeof j==="object")switch(typeof j.tag==="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),j.$$typeof){case Z0:return"Portal";case n:return j.displayName||"Context";case i:return(j._context.displayName||"Context")+".Consumer";case L0:var Y0=j.render;return j=j.displayName,j||(j=Y0.displayName||Y0.name||"",j=j!==""?"ForwardRef("+j+")":"ForwardRef"),j;case p0:return Y0=j.displayName||null,Y0!==null?Y0:M(j.type)||"Memo";case i0:Y0=j._payload,j=j._init;try{return M(j(Y0))}catch(A0){}}return null}function H(j){if(j===K0)return"<>";if(typeof j==="object"&&j!==null&&j.$$typeof===i0)return"<...>";try{var Y0=M(j);return Y0?"<"+Y0+">":"<...>"}catch(A0){return"<...>"}}function O(){var j=W1.A;return j===null?null:j.getOwner()}function _(){return Error("react-stack-top-frame")}function A(j){if(N4.call(j,"key")){var Y0=Object.getOwnPropertyDescriptor(j,"key").get;if(Y0&&Y0.isReactWarning)return!1}return j.key!==void 0}function P(j,Y0){function A0(){V6||(V6=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",Y0))}A0.isReactWarning=!0,Object.defineProperty(j,"key",{get:A0,configurable:!0})}function L(){var j=M(this.type);return d4[j]||(d4[j]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),j=this.props.ref,j!==void 0?j:null}function v(j,Y0,A0,T0,k0,Y1){var f0=A0.ref;return j={$$typeof:G0,type:j,key:Y0,props:A0,_owner:T0},(f0!==void 0?f0:null)!==null?Object.defineProperty(j,"ref",{enumerable:!1,get:L}):Object.defineProperty(j,"ref",{enumerable:!1,value:null}),j._store={},Object.defineProperty(j._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(j,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(j,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:k0}),Object.defineProperty(j,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:Y1}),Object.freeze&&(Object.freeze(j.props),Object.freeze(j)),j}function R(j,Y0){return Y0=v(j.type,Y0,j.props,j._owner,j._debugStack,j._debugTask),j._store&&(Y0._store.validated=j._store.validated),Y0}function T(j){C(j)?j._store&&(j._store.validated=1):typeof j==="object"&&j!==null&&j.$$typeof===i0&&(j._payload.status==="fulfilled"?C(j._payload.value)&&j._payload.value._store&&(j._payload.value._store.validated=1):j._store&&(j._store.validated=1))}function C(j){return typeof j==="object"&&j!==null&&j.$$typeof===G0}function y(j){var Y0={"=":"=0",":":"=2"};return"$"+j.replace(/[=:]/g,function(A0){return Y0[A0]})}function b(j,Y0){return typeof j==="object"&&j!==null&&j.key!=null?(U(j.key),y(""+j.key)):Y0.toString(36)}function S(j){switch(j.status){case"fulfilled":return j.value;case"rejected":throw j.reason;default:switch(typeof j.status==="string"?j.then(W,W):(j.status="pending",j.then(function(Y0){j.status==="pending"&&(j.status="fulfilled",j.value=Y0)},function(Y0){j.status==="pending"&&(j.status="rejected",j.reason=Y0)})),j.status){case"fulfilled":return j.value;case"rejected":throw j.reason}}throw j}function I(j,Y0,A0,T0,k0){var Y1=typeof j;if(Y1==="undefined"||Y1==="boolean")j=null;var f0=!1;if(j===null)f0=!0;else switch(Y1){case"bigint":case"string":case"number":f0=!0;break;case"object":switch(j.$$typeof){case G0:case Z0:f0=!0;break;case i0:return f0=j._init,I(f0(j._payload),Y0,A0,T0,k0)}}if(f0){f0=j,k0=k0(f0);var Q1=T0===""?"."+b(f0,0):T0;return H1(k0)?(A0="",Q1!=null&&(A0=Q1.replace(Y7,"$&/")+"/"),I(k0,Y0,A0,"",function(_5){return _5})):k0!=null&&(C(k0)&&(k0.key!=null&&(f0&&f0.key===k0.key||U(k0.key)),A0=R(k0,A0+(k0.key==null||f0&&f0.key===k0.key?"":(""+k0.key).replace(Y7,"$&/")+"/")+Q1),T0!==""&&f0!=null&&C(f0)&&f0.key==null&&f0._store&&!f0._store.validated&&(A0._store.validated=2),k0=A0),Y0.push(k0)),1}if(f0=0,Q1=T0===""?".":T0+":",H1(j))for(var y0=0;y0 import('./MyComponent')) - -Did you accidentally put curly braces around the import?`,Y0),"default"in Y0||console.error(`lazy: Expected the result of a dynamic import() call. Instead received: %s - -Your code should look like: - const MyComponent = lazy(() => import('./MyComponent'))`,Y0),Y0.default;throw j._result}function k(){var j=W1.H;return j===null&&console.error(`Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: -1. You might have mismatching versions of React and the renderer (such as React DOM) -2. You might be breaking the Rules of Hooks -3. You might have more than one copy of React in the same app -See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.`),j}function o(){W1.asyncTransitions--}function q0(j){if(O4===null)try{var Y0=("require"+Math.random()).slice(0,7);O4=(YU&&YU[Y0]).call(YU,"timers").setImmediate}catch(A0){O4=function(T0){$2===!1&&($2=!0,typeof MessageChannel>"u"&&console.error("This browser does not have a MessageChannel implementation, so enqueuing tasks via await act(async () => ...) will fail. Please file an issue at https://github.com/facebook/react/issues if you encounter this warning."));var k0=new MessageChannel;k0.port1.onmessage=T0,k0.port2.postMessage(void 0)}}return O4(j)}function $0(j){return 1 ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"))}),{then:function(y0,_5){k0=!0,f0.then(function(K5){if(w0(Y0,A0),A0===0){try{X0(T0),q0(function(){return e(K5,y0,_5)})}catch(e5){W1.thrownErrors.push(e5)}if(0 ...)"))}),W1.actQueue=null),0W1.recentlyCreatedOwnerStacks++;return v(j,k0,T0,O(),y0?Error("react-stack-top-frame"):x2,y0?S1(H(j)):L6)},va.createRef=function(){var j={current:null};return Object.seal(j),j},va.forwardRef=function(j){j!=null&&j.$$typeof===p0?console.error("forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))."):typeof j!=="function"?console.error("forwardRef requires a render function but was given %s.",j===null?"null":typeof j):j.length!==0&&j.length!==2&&console.error("forwardRef render functions accept exactly two parameters: props and ref. %s",j.length===1?"Did you forget to use the ref parameter?":"Any additional parameter will be undefined."),j!=null&&j.defaultProps!=null&&console.error("forwardRef render functions do not support defaultProps. Did you accidentally pass a React component?");var Y0={$$typeof:L0,render:j},A0;return Object.defineProperty(Y0,"displayName",{enumerable:!1,configurable:!0,get:function(){return A0},set:function(T0){A0=T0,j.name||j.displayName||(Object.defineProperty(j,"name",{value:T0}),j.displayName=T0)}}),Y0},va.isValidElement=C,va.lazy=function(j){j={_status:-1,_result:j};var Y0={$$typeof:i0,_payload:j,_init:l},A0={name:"lazy",start:-1,end:-1,value:null,owner:null,debugStack:Error("react-stack-top-frame"),debugTask:console.createTask?console.createTask("lazy()"):null};return j._ioInfo=A0,Y0._debugInfo=[{awaited:A0}],Y0},va.memo=function(j,Y0){j==null&&console.error("memo: The first argument must be a component. Instead received: %s",j===null?"null":typeof j),Y0={$$typeof:p0,type:j,compare:Y0===void 0?null:Y0};var A0;return Object.defineProperty(Y0,"displayName",{enumerable:!1,configurable:!0,get:function(){return A0},set:function(T0){A0=T0,j.name||j.displayName||(Object.defineProperty(j,"name",{value:T0}),j.displayName=T0)}}),Y0},va.startTransition=function(j){var Y0=W1.T,A0={};A0._updatedFibers=new Set,W1.T=A0;try{var T0=j(),k0=W1.S;k0!==null&&k0(A0,T0),typeof T0==="object"&&T0!==null&&typeof T0.then==="function"&&(W1.asyncTransitions++,T0.then(o,o),T0.then(W,C5))}catch(Y1){C5(Y1)}finally{Y0===null&&A0._updatedFibers&&(j=A0._updatedFibers.size,A0._updatedFibers.clear(),10{(function(){function Z(){if(y=!1,g){var e=Ca.unstable_now();o=e;var X0=!0;try{Z:{T=!1,C&&(C=!1,S(l),l=-1),R=!0;var G0=v;try{Y:{B(e);for(L=Q(_);L!==null&&!(L.expirationTime>e&&$());){var Z0=L.callback;if(typeof Z0==="function"){L.callback=null,v=L.priorityLevel;var K0=Z0(L.expirationTime<=e);if(e=Ca.unstable_now(),typeof K0==="function"){L.callback=K0,B(e),X0=!0;break Y}L===Q(_)&&z(_),B(e)}else z(_);L=Q(_)}if(L!==null)X0=!0;else{var f=Q(A);f!==null&&U(W,f.startTime-e),X0=!1}}break Z}finally{L=null,v=G0,R=!1}X0=void 0}}finally{X0?q0():g=!1}}}function Y(e,X0){var G0=e.length;e.push(X0);Z:for(;0>>1,K0=e[Z0];if(0>>1;Z0q(i,G0))nq(L0,i)?(e[Z0]=L0,e[n]=G0,Z0=n):(e[Z0]=i,e[z0]=G0,Z0=z0);else if(nq(L0,G0))e[Z0]=L0,e[n]=G0,Z0=n;else break Z}}return X0}function q(e,X0){var G0=e.sortIndex-X0.sortIndex;return G0!==0?G0:e.id-X0.id}function B(e){for(var X0=Q(A);X0!==null;){if(X0.callback===null)z(A);else if(X0.startTime<=e)z(A),X0.sortIndex=X0.expirationTime,Y(_,X0);else break;X0=Q(A)}}function W(e){if(C=!1,B(e),!T)if(Q(_)!==null)T=!0,g||(g=!0,q0());else{var X0=Q(A);X0!==null&&U(W,X0.startTime-e)}}function $(){return y?!0:Ca.unstable_now()-oe||125Z0?(e.sortIndex=G0,Y(A,e),Q(_)===null&&e===Q(A)&&(C?(S(l),l=-1):C=!0,U(W,G0-Z0))):(e.sortIndex=K0,Y(_,e),T||R||(T=!0,g||(g=!0,q0()))),e},Ca.unstable_shouldYield=$,Ca.unstable_wrapCallback=function(e){var X0=v;return function(){var G0=v;v=X0;try{return e.apply(this,arguments)}finally{v=G0}}},typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop==="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error())})()});var dI=D3((Da)=>{var dA=h(J0());(function(){function Z(){}function Y(H){return""+H}function Q(H,O,_){var A=3` tag.%s',_),typeof H==="string"&&typeof O==="object"&&O!==null&&typeof O.as==="string"){_=O.as;var A=z(_,O.crossOrigin);$.d.L(H,_,{crossOrigin:A,integrity:typeof O.integrity==="string"?O.integrity:void 0,nonce:typeof O.nonce==="string"?O.nonce:void 0,type:typeof O.type==="string"?O.type:void 0,fetchPriority:typeof O.fetchPriority==="string"?O.fetchPriority:void 0,referrerPolicy:typeof O.referrerPolicy==="string"?O.referrerPolicy:void 0,imageSrcSet:typeof O.imageSrcSet==="string"?O.imageSrcSet:void 0,imageSizes:typeof O.imageSizes==="string"?O.imageSizes:void 0,media:typeof O.media==="string"?O.media:void 0})}},Da.preloadModule=function(H,O){var _="";typeof H==="string"&&H||(_+=" The `href` argument encountered was "+q(H)+"."),O!==void 0&&typeof O!=="object"?_+=" The `options` argument encountered was "+q(O)+".":O&&("as"in O)&&typeof O.as!=="string"&&(_+=" The `as` option encountered was "+q(O.as)+"."),_&&console.error('ReactDOM.preloadModule(): Expected two arguments, a non-empty `href` string and, optionally, an `options` object with an `as` property valid for a `` tag.%s',_),typeof H==="string"&&(O?(_=z(O.as,O.crossOrigin),$.d.m(H,{as:typeof O.as==="string"&&O.as!=="script"?O.as:void 0,crossOrigin:_,integrity:typeof O.integrity==="string"?O.integrity:void 0})):$.d.m(H))},Da.requestFormReset=function(H){$.d.r(H)},Da.unstable_batchedUpdates=function(H,O){return H(O)},Da.useFormState=function(H,O,_){return W().useFormState(H,O,_)},Da.useFormStatus=function(){return W().useHostTransitionStatus()},Da.version="19.2.4",typeof __REACT_DEVTOOLS_GLOBAL_HOOK__<"u"&&typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop==="function"&&__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error())})()});var b3=D3((hQ0,lI)=>{var ba=h(dI());lI.exports=ba});var rI=D3((Ea)=>{var a1=h(cI()),oQ=h(J0()),lA=h(b3());(function(){function Z(J,X){for(J=J.memoizedState;J!==null&&0=X.length)return G;var w=X[K],N=T2(J)?J.slice():E1({},J);return N[w]=Y(J[w],X,K+1,G),N}function Q(J,X,K){if(X.length!==K.length)console.warn("copyWithRename() expects paths of the same length");else{for(var G=0;GA8?console.error("Unexpected pop."):(X!==V_[A8]&&console.error("Unexpected Fiber popped."),J.current=P_[A8],P_[A8]=null,V_[A8]=null,A8--)}function $0(J,X,K){A8++,P_[A8]=J.current,V_[A8]=K,J.current=X}function w0(J){return J===null&&console.error("Expected host context to exist. This error is likely caused by a bug in React. Please file an issue."),J}function e(J,X){$0(y9,X,J),$0(sq,J,J),$0(I9,null,J);var K=X.nodeType;switch(K){case 9:case 11:K=K===9?"#document":"#fragment",X=(X=X.documentElement)?(X=X.namespaceURI)?vD(X):j8:j8;break;default:if(K=X.tagName,X=X.namespaceURI)X=vD(X),X=CD(X,K);else switch(K){case"svg":X=aQ;break;case"math":X=sG;break;default:X=j8}}K=K.toLowerCase(),K=FT(null,K),K={context:X,ancestorInfo:K},q0(I9,J),$0(I9,K,J)}function X0(J){q0(I9,J),q0(sq,J),q0(y9,J)}function G0(){return w0(I9.current)}function Z0(J){J.memoizedState!==null&&$0(t$,J,J);var X=w0(I9.current),K=J.type,G=CD(X.context,K);K=FT(X.ancestorInfo,K),G={context:G,ancestorInfo:K},X!==G&&($0(sq,J,J),$0(I9,G,J))}function K0(J){sq.current===J&&(q0(I9,J),q0(sq,J)),t$.current===J&&(q0(t$,J),gK._currentValue=bY)}function f(){}function z0(){if(oq===0){Qb=console.log,Xb=console.info,zb=console.warn,qb=console.error,Kb=console.group,Bb=console.groupCollapsed,Wb=console.groupEnd;var J={configurable:!0,enumerable:!0,value:f,writable:!0};Object.defineProperties(console,{info:J,log:J,warn:J,error:J,group:J,groupCollapsed:J,groupEnd:J})}oq++}function i(){if(oq--,oq===0){var J={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:E1({},J,{value:Qb}),info:E1({},J,{value:Xb}),warn:E1({},J,{value:zb}),error:E1({},J,{value:qb}),group:E1({},J,{value:Kb}),groupCollapsed:E1({},J,{value:Bb}),groupEnd:E1({},J,{value:Wb})})}0>oq&&console.error("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}function n(J){var X=Error.prepareStackTrace;if(Error.prepareStackTrace=void 0,J=J.stack,Error.prepareStackTrace=X,J.startsWith(`Error: react-stack-top-frame -`)&&(J=J.slice(29)),X=J.indexOf(` -`),X!==-1&&(J=J.slice(X+1)),X=J.indexOf("react_stack_bottom_frame"),X!==-1&&(X=J.lastIndexOf(` -`,X)),X!==-1)J=J.slice(0,X);else return"";return J}function L0(J){if(L_===void 0)try{throw Error()}catch(K){var X=K.stack.trim().match(/\n( *(at )?)/);L_=X&&X[1]||"",$b=-1)":-1F||E[N]!==a[F]){var s=` -`+E[N].replace(" at new "," at ");return J.displayName&&s.includes("")&&(s=s.replace("",J.displayName)),typeof J==="function"&&T_.set(J,s),s}while(1<=N&&0<=F);break}}}finally{R_=!1,M0.H=G,i(),Error.prepareStackTrace=K}return E=(E=J?J.displayName||J.name:"")?L0(E):"",typeof J==="function"&&T_.set(J,E),E}function H0(J,X){switch(J.tag){case 26:case 27:case 5:return L0(J.type);case 16:return L0("Lazy");case 13:return J.child!==X&&X!==null?L0("Suspense Fallback"):L0("Suspense");case 19:return L0("SuspenseList");case 0:case 15:return x0(J.type,!1);case 11:return x0(J.type.render,!1);case 1:return x0(J.type,!0);case 31:return L0("Activity");default:return""}}function p0(J){try{var X="",K=null;do{X+=H0(J,K);var G=J._debugInfo;if(G)for(var w=G.length-1;0<=w;w--){var N=G[w];if(typeof N.name==="string"){var F=X;Z:{var{name:V,env:D,debugLocation:E}=N;if(E!=null){var a=n(E),s=a.lastIndexOf(` -`),c=s===-1?a:a.slice(s+1);if(c.indexOf(V)!==-1){var B0=` -`+c;break Z}}B0=L0(V+(D?" ["+D+"]":""))}X=F+B0}}K=J,J=J.return}while(J);return X}catch(v0){return` -Error generating stack: `+v0.message+` -`+v0.stack}}function i0(J){return(J=J?J.displayName||J.name:"")?L0(J):""}function R1(){if(v4===null)return null;var J=v4._debugOwner;return J!=null?l(J):null}function B1(){if(v4===null)return"";var J=v4;try{var X="";switch(J.tag===6&&(J=J.return),J.tag){case 26:case 27:case 5:X+=L0(J.type);break;case 13:X+=L0("Suspense");break;case 19:X+=L0("SuspenseList");break;case 31:X+=L0("Activity");break;case 30:case 0:case 15:case 1:J._debugOwner||X!==""||(X+=i0(J.type));break;case 11:J._debugOwner||X!==""||(X+=i0(J.type.render))}for(;J;)if(typeof J.tag==="number"){var K=J;J=K._debugOwner;var G=K._debugStack;if(J&&G){var w=n(G);w!==""&&(X+=` -`+w)}}else if(J.debugStack!=null){var N=J.debugStack;(J=J.owner)&&N&&(X+=` -`+n(N))}else break;var F=X}catch(V){F=` -Error generating stack: `+V.message+` -`+V.stack}return F}function R0(J,X,K,G,w,N,F){var V=v4;C1(J);try{return J!==null&&J._debugTask?J._debugTask.run(X.bind(null,K,G,w,N,F)):X(K,G,w,N,F)}finally{C1(V)}throw Error("runWithFiberInDEV should never be called in production. This is a bug in React.")}function C1(J){M0.getCurrentStack=J===null?null:B1,_3=!1,v4=J}function X5(J){return typeof Symbol==="function"&&Symbol.toStringTag&&J[Symbol.toStringTag]||J.constructor.name||"Object"}function n5(J){try{return O5(J),!1}catch(X){return!0}}function O5(J){return""+J}function H1(J,X){if(n5(J))return console.error("The provided `%s` attribute is an unsupported type %s. This value must be coerced to a string before using it here.",X,X5(J)),O5(J)}function t6(J,X){if(n5(J))return console.error("The provided `%s` CSS property is an unsupported type %s. This value must be coerced to a string before using it here.",X,X5(J)),O5(J)}function W1(J){if(n5(J))return console.error("Form field values (value, checked, defaultValue, or defaultChecked props) must be strings, not %s. This value must be coerced to a string before using it here.",X5(J)),O5(J)}function N4(J){if(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u")return!1;var X=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(X.isDisabled)return!0;if(!X.supportsFiber)return console.error("The installed version of React DevTools is too old and will not work with the current version of React. Please update React DevTools. https://react.dev/link/react-devtools"),!0;try{HQ=X.inject(J),F7=X}catch(K){console.error("React instrumentation encountered an error: %o.",K)}return X.checkDCE?!0:!1}function S1(J){if(typeof cl==="function"&&dl(J),F7&&typeof F7.setStrictMode==="function")try{F7.setStrictMode(HQ,J)}catch(X){A3||(A3=!0,console.error("React instrumentation encountered an error: %o",X))}}function V6(J){return J>>>=0,J===0?32:31-(ll(J)/rl|0)|0}function Z7(J){var X=J&42;if(X!==0)return X;switch(J&-J){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return J&261888;case 262144:case 524288:case 1048576:case 2097152:return J&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return J&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return console.error("Should have found matching lanes. This is a bug in React."),J}}function d4(J,X,K){var G=J.pendingLanes;if(G===0)return 0;var w=0,N=J.suspendedLanes,F=J.pingedLanes;J=J.warmLanes;var V=G&134217727;return V!==0?(G=V&~N,G!==0?w=Z7(G):(F&=V,F!==0?w=Z7(F):K||(K=V&~J,K!==0&&(w=Z7(K))))):(V=G&~N,V!==0?w=Z7(V):F!==0?w=Z7(F):K||(K=G&~J,K!==0&&(w=Z7(K)))),w===0?0:X!==0&&X!==w&&(X&N)===0&&(N=w&-w,K=X&-X,N>=K||N===32&&(K&4194048)!==0)?X:w}function x2(J,X){return(J.pendingLanes&~(J.suspendedLanes&~J.pingedLanes)&X)===0}function L6(J,X){switch(J){case 1:case 2:case 4:case 8:case 64:return X+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return X+5000;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return console.error("Should have found matching lanes. This is a bug in React."),-1}}function R6(){var J=ZG;return ZG<<=1,(ZG&62914560)===0&&(ZG=4194304),J}function Y7(J){for(var X=[],K=0;31>K;K++)X.push(J);return X}function C5(J,X){J.pendingLanes|=X,X!==268435456&&(J.suspendedLanes=0,J.pingedLanes=0,J.warmLanes=0)}function $2(J,X,K,G,w,N){var F=J.pendingLanes;J.pendingLanes=K,J.suspendedLanes=0,J.pingedLanes=0,J.warmLanes=0,J.expiredLanes&=K,J.entangledLanes&=K,J.errorRecoveryDisabledLanes&=K,J.shellSuspendCounter=0;var{entanglements:V,expirationTimes:D,hiddenUpdates:E}=J;for(K=F&~K;0"u")return null;try{return J.activeElement||J.body}catch(X){return J.body}}function S0(J){return J.replace(tl,function(X){return"\\"+X.charCodeAt(0).toString(16)+" "})}function l0(J,X){X.checked===void 0||X.defaultChecked===void 0||Nb||(console.error("%s contains an input of type %s with both checked and defaultChecked props. Input elements must be either controlled or uncontrolled (specify either the checked prop, or the defaultChecked prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://react.dev/link/controlled-components",R1()||"A component",X.type),Nb=!0),X.value===void 0||X.defaultValue===void 0||Hb||(console.error("%s contains an input of type %s with both value and defaultValue props. Input elements must be either controlled or uncontrolled (specify either the value prop, or the defaultValue prop, but not both). Decide between using a controlled or uncontrolled input element and remove one of these props. More info: https://react.dev/link/controlled-components",R1()||"A component",X.type),Hb=!0)}function a0(J,X,K,G,w,N,F,V){if(J.name="",F!=null&&typeof F!=="function"&&typeof F!=="symbol"&&typeof F!=="boolean"?(H1(F,"type"),J.type=F):J.removeAttribute("type"),X!=null)if(F==="number"){if(X===0&&J.value===""||J.value!=X)J.value=""+t(X)}else J.value!==""+t(X)&&(J.value=""+t(X));else F!=="submit"&&F!=="reset"||J.removeAttribute("value");X!=null?r0(J,F,t(X)):K!=null?r0(J,F,t(K)):G!=null&&J.removeAttribute("value"),w==null&&N!=null&&(J.defaultChecked=!!N),w!=null&&(J.checked=w&&typeof w!=="function"&&typeof w!=="symbol"),V!=null&&typeof V!=="function"&&typeof V!=="symbol"&&typeof V!=="boolean"?(H1(V,"name"),J.name=""+t(V)):J.removeAttribute("name")}function J1(J,X,K,G,w,N,F,V){if(N!=null&&typeof N!=="function"&&typeof N!=="symbol"&&typeof N!=="boolean"&&(H1(N,"type"),J.type=N),X!=null||K!=null){if(!(N!=="submit"&&N!=="reset"||X!==void 0&&X!==null)){P0(J);return}K=K!=null?""+t(K):"",X=X!=null?""+t(X):K,V||X===J.value||(J.value=X),J.defaultValue=X}G=G!=null?G:w,G=typeof G!=="function"&&typeof G!=="symbol"&&!!G,J.checked=V?J.checked:!!G,J.defaultChecked=!!G,F!=null&&typeof F!=="function"&&typeof F!=="symbol"&&typeof F!=="boolean"&&(H1(F,"name"),J.name=F),P0(J)}function r0(J,X,K){X==="number"&&j0(J.ownerDocument)===J||J.defaultValue===""+K||(J.defaultValue=""+K)}function p1(J,X){X.value==null&&(typeof X.children==="object"&&X.children!==null?oQ.Children.forEach(X.children,function(K){K==null||typeof K==="string"||typeof K==="number"||typeof K==="bigint"||_b||(_b=!0,console.error("Cannot infer the option value of complex children. Pass a `value` prop or use a plain string as children to