improve ai alert copilot ux
This commit is contained in:
parent
ebdc4ab8e6
commit
32e965d782
15 changed files with 931 additions and 15 deletions
|
|
@ -18,6 +18,7 @@
|
|||
{"_type":"issue","id":"islandflow-ayo","title":"Drop stale backlog events from live fanout","description":"Follow-up to live freshness rollout: /ws/live was still fanning out stale backlog events for freshness-gated channels, which kept tape panes in Live feed behind despite active synthetic ingest. Gate fanout and cache ingest by freshness for options/nbbo/equities/flow.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:26:39Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:26:44Z","started_at":"2026-04-28T21:26:44Z","closed_at":"2026-04-28T21:26:44Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"islandflow-0v6","title":"Fix tape freshness, NBBO coverage, pause controls, and filter popup","description":"Implement the tape fixes requested for synthetic options notional sizing, strict live freshness, live-mode pause/resume behavior, stronger NBBO snapshot coverage, and moving flow filters behind a popup. Includes server-side live cache changes, web terminal state/UI changes, and tests for synthetic pricing, live snapshot freshness/NBBO retention, and live pause/filter interactions.","status":"closed","priority":1,"issue_type":"task","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T21:02:52Z","created_by":"dirtydishes","updated_at":"2026-04-28T21:13:38Z","started_at":"2026-04-28T21:02:57Z","closed_at":"2026-04-28T21:13:38Z","close_reason":"Completed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"islandflow-e4r","title":"Implement smart-money flow filtering and synthetic firehose modes","description":"Implement the approved multi-surface plan for named synthetic market profiles, options raw-vs-signal filtering, live/API filter contracts, Tape page client-side flow filters, firehose-readiness improvements, tests, and README updates.","status":"closed","priority":1,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-04-28T20:10:49Z","created_by":"dirtydishes","updated_at":"2026-04-28T20:29:29Z","started_at":"2026-04-28T20:10:53Z","closed_at":"2026-04-28T20:29:29Z","close_reason":"Implemented synthetic market profiles, options signal-path filtering, signal-aware API/replay contracts, Tape page filters, tests, and README updates. Follow-up tracked in islandflow-biq.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"islandflow-xtg","title":"implement ai alert copilot ux refinements","description":"Implement the AI alert Copilot UX plan: markdown result rendering, reusable task result states, in-session result caching with regenerate, task cancellation through the desktop bridge, tests, and required turn documentation.","status":"closed","priority":2,"issue_type":"feature","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T23:30:50Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:37:58Z","started_at":"2026-05-20T23:30:58Z","closed_at":"2026-05-20T23:37:58Z","close_reason":"Implemented markdown Copilot rendering, session result caching, regenerate controls, task cancellation plumbing, tests, and turn documentation.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"islandflow-dy2","title":"Clarify desktop AI settings when bridge is unavailable","description":"The /settings desktop AI panel currently renders disabled ChatGPT login buttons and empty-feeling model controls when the native bridge is unavailable. Users read this as broken UI because the controls do not clearly explain that the desktop shell is missing its bridge session and therefore cannot load login or model options. Update the settings surface to explain the unavailable state, provide direct recovery guidance, and make disabled controls self-explanatory.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T22:56:03Z","created_by":"dirtydishes","updated_at":"2026-05-20T23:01:33Z","started_at":"2026-05-20T22:56:26Z","closed_at":"2026-05-20T23:01:33Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"islandflow-c8f","title":"fix packages/types ts-extension imports for next build","description":"## Why\\nThe web production build fails during type-checking because packages/types/src/desktop-ai.ts imports sibling files with explicit .ts extensions, which Next's TypeScript config rejects without allowImportingTsExtensions.\\n\\n## What\\nNormalize the packages/types import specifiers so Next can type-check the shared package during app builds, or adjust the shared tsconfig/build strategy in a deliberate way.\\n\\n## Acceptance Criteria\\n- bun --cwd=apps/web run build no longer fails on .ts-extension import paths from packages/types\\n- The chosen import-specifier strategy is consistent across packages/types","status":"open","priority":2,"issue_type":"bug","owner":"dishes@dpdrm.com","created_at":"2026-05-20T22:35:30Z","created_by":"dirtydishes","updated_at":"2026-05-20T22:35:30Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"issue","id":"islandflow-64s","title":"Fix desktop startup failure from @islandflow/types ESM imports","description":"Electron desktop startup fails with ERR_MODULE_NOT_FOUND because @islandflow/types exports TypeScript source and internal relative imports lacked .ts extensions under Node/Electron ESM resolution. Update type package internal imports and desktop tsconfig so desktop build and runtime can resolve modules consistently.","status":"closed","priority":2,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-20T22:26:45Z","created_by":"dirtydishes","updated_at":"2026-05-20T22:28:05Z","started_at":"2026-05-20T22:26:50Z","closed_at":"2026-05-20T22:28:05Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
|
|
|
|||
|
|
@ -156,8 +156,15 @@ Use a clear timestamped filename:
|
|||
```text
|
||||
docs/plans/YYYY-MM-DD-short-plan-name.html
|
||||
```
|
||||
### Format
|
||||
|
||||
The plan document should be labeled clearly as a plan and should include:
|
||||
Use the `impeccable` skill to structure and style the document as clean, readable HTML.
|
||||
|
||||
For this repository, `impeccable` is the styling and layout authority for turn documents when available. Do not apply global non-repo computer-task house styling to repository turn documents.
|
||||
|
||||
If the `impeccable` skill is unavailable or blocked by an actual tool/file error, still create a well-structured standalone HTML file.
|
||||
|
||||
Plan documents should be labeled clearly as a plan and should include:
|
||||
|
||||
1. **Plan Summary**
|
||||
2. **Goals**
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ export const DESKTOP_AI_CANCEL_LOGIN = "islandflow:desktop-ai:cancel-login";
|
|||
export const DESKTOP_AI_LOGOUT = "islandflow:desktop-ai:logout";
|
||||
export const DESKTOP_AI_UPDATE_PREFERENCES = "islandflow:desktop-ai:update-preferences";
|
||||
export const DESKTOP_AI_RUN_TASK = "islandflow:desktop-ai:run-task";
|
||||
export const DESKTOP_AI_CANCEL_TASK = "islandflow:desktop-ai:cancel-task";
|
||||
|
|
|
|||
|
|
@ -171,4 +171,65 @@ describe("desktop ai usage and state tracking", () => {
|
|||
expect(service.getState().account.planType).toBeNull();
|
||||
expect(service.getState().account.login).toEqual({ status: "idle", message: "Logged out." });
|
||||
});
|
||||
|
||||
it("marks active tasks cancelled and ignores later deltas or completion", async () => {
|
||||
const dir = await makeTempDir();
|
||||
const service = new IslandflowDesktopAiService(dir, async () => {}, () => {});
|
||||
const internal = service as any;
|
||||
const requests: Array<{ method: string; params: unknown }> = [];
|
||||
|
||||
internal.client = {
|
||||
request: async (method: string, params: unknown) => {
|
||||
requests.push({ method, params });
|
||||
return {};
|
||||
}
|
||||
};
|
||||
internal.state.tasks = [
|
||||
{
|
||||
taskId: "task-1",
|
||||
kind: "smart-money-explain",
|
||||
title: "Explain smart money event",
|
||||
subtitle: "AAPL",
|
||||
status: "running",
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
model: "gpt-5.4",
|
||||
reasoningEffort: "high",
|
||||
text: "partial",
|
||||
error: null,
|
||||
compiledScreen: null
|
||||
}
|
||||
];
|
||||
internal.activeTasksByThreadId.set("thread-1", {
|
||||
taskId: "task-1",
|
||||
taskKind: "smart-money-explain",
|
||||
taskTitle: "Explain smart money event",
|
||||
profileId: "managed-chatgpt"
|
||||
});
|
||||
|
||||
await service.cancelTask("task-1");
|
||||
await internal.handleNotification("item/agentMessage/delta", {
|
||||
threadId: "thread-1",
|
||||
delta: " late delta"
|
||||
});
|
||||
await internal.handleNotification("turn/completed", {
|
||||
threadId: "thread-1",
|
||||
turn: {
|
||||
id: "turn-1",
|
||||
status: "completed",
|
||||
error: null
|
||||
}
|
||||
});
|
||||
|
||||
expect(requests).toEqual([
|
||||
{
|
||||
method: "turn/cancel",
|
||||
params: { threadId: "thread-1", turnId: "turn-1" }
|
||||
}
|
||||
]);
|
||||
expect(service.getState().tasks[0]?.status).toBe("cancelled");
|
||||
expect(service.getState().tasks[0]?.text).toBe("partial");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -650,6 +650,7 @@ export class IslandflowDesktopAiService {
|
|||
private readonly sandboxCwd: string;
|
||||
private readonly client: CodexAppServerClient;
|
||||
private readonly activeTasksByThreadId = new Map<string, ActiveTaskContext>();
|
||||
private readonly locallyCancelledTaskIds = new Set<string>();
|
||||
private usageStore: PersistedUsageStore = createUsageStore();
|
||||
private state: IslandflowAiState = createInitialState();
|
||||
private serviceTier: string | null = null;
|
||||
|
|
@ -851,6 +852,32 @@ export class IslandflowDesktopAiService {
|
|||
}
|
||||
}
|
||||
|
||||
async cancelTask(taskId: string): Promise<void> {
|
||||
const task = this.state.tasks.find((candidate) => candidate.taskId === taskId);
|
||||
if (!task || (task.status !== "queued" && task.status !== "running")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.locallyCancelledTaskIds.add(taskId);
|
||||
this.patchTask(taskId, {
|
||||
status: "cancelled",
|
||||
error: null
|
||||
});
|
||||
|
||||
const threadId = task.threadId;
|
||||
if (!threadId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeTasksByThreadId.delete(threadId);
|
||||
const params = {
|
||||
threadId,
|
||||
turnId: task.turnId ?? undefined
|
||||
};
|
||||
|
||||
await this.client.request("turn/cancel", params).catch(() => undefined);
|
||||
}
|
||||
|
||||
private async ensureClientReady(): Promise<void> {
|
||||
const selectedProfile = this.resolveSelectedProfileMode();
|
||||
this.state.transportStatus = this.state.transportStatus === "restarting" ? "restarting" : "starting";
|
||||
|
|
@ -979,6 +1006,9 @@ export class IslandflowDesktopAiService {
|
|||
if (!activeTask) {
|
||||
return;
|
||||
}
|
||||
if (this.locallyCancelledTaskIds.has(activeTask.taskId)) {
|
||||
return;
|
||||
}
|
||||
const current = this.state.tasks.find((task) => task.taskId === activeTask.taskId);
|
||||
if (!current) {
|
||||
return;
|
||||
|
|
@ -1000,6 +1030,9 @@ export class IslandflowDesktopAiService {
|
|||
if (!activeTask) {
|
||||
return;
|
||||
}
|
||||
if (this.locallyCancelledTaskIds.has(activeTask.taskId)) {
|
||||
return;
|
||||
}
|
||||
if (typeof payload.item.text === "string") {
|
||||
this.patchTask(activeTask.taskId, {
|
||||
text: payload.item.text
|
||||
|
|
@ -1032,6 +1065,10 @@ export class IslandflowDesktopAiService {
|
|||
if (!activeTask) {
|
||||
return;
|
||||
}
|
||||
if (this.locallyCancelledTaskIds.has(activeTask.taskId)) {
|
||||
this.activeTasksByThreadId.delete(payload.threadId);
|
||||
return;
|
||||
}
|
||||
const current = this.state.tasks.find((task) => task.taskId === activeTask.taskId);
|
||||
if (!current) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { IslandflowDesktopAiService } from "./desktop-ai.js";
|
||||
import {
|
||||
DESKTOP_AI_CANCEL_LOGIN,
|
||||
DESKTOP_AI_CANCEL_TASK,
|
||||
DESKTOP_AI_GET_STATE,
|
||||
DESKTOP_AI_LOGIN_BROWSER,
|
||||
DESKTOP_AI_LOGIN_DEVICE,
|
||||
|
|
@ -168,6 +169,11 @@ const registerDesktopAiIpc = (service: IslandflowDesktopAiService): void => {
|
|||
guard(event);
|
||||
return service.runTask(request);
|
||||
});
|
||||
|
||||
ipcMain.handle(DESKTOP_AI_CANCEL_TASK, async (event, taskId) => {
|
||||
guard(event);
|
||||
await service.cancelTask(String(taskId));
|
||||
});
|
||||
};
|
||||
|
||||
const ensureMainWindow = (): void => {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const DESKTOP_AI_CANCEL_LOGIN = "islandflow:desktop-ai:cancel-login";
|
|||
const DESKTOP_AI_LOGOUT = "islandflow:desktop-ai:logout";
|
||||
const DESKTOP_AI_UPDATE_PREFERENCES = "islandflow:desktop-ai:update-preferences";
|
||||
const DESKTOP_AI_RUN_TASK = "islandflow:desktop-ai:run-task";
|
||||
const DESKTOP_AI_CANCEL_TASK = "islandflow:desktop-ai:cancel-task";
|
||||
|
||||
type DesktopAiState = any;
|
||||
type DesktopAiTaskRequest = any;
|
||||
|
|
@ -27,6 +28,8 @@ const bridge = {
|
|||
ipcRenderer.invoke(DESKTOP_AI_UPDATE_PREFERENCES, next),
|
||||
runTask: (request: DesktopAiTaskRequest): Promise<{ taskId: string }> =>
|
||||
ipcRenderer.invoke(DESKTOP_AI_RUN_TASK, request),
|
||||
cancelTask: (taskId: string): Promise<void> =>
|
||||
ipcRenderer.invoke(DESKTOP_AI_CANCEL_TASK, taskId),
|
||||
subscribe: (listener: (state: DesktopAiState) => void): (() => void) => {
|
||||
const handler = (_event: Electron.IpcRendererEvent, state: DesktopAiState) => {
|
||||
listener(state);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState, type ReactNode } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import type {
|
||||
AlertEvent,
|
||||
ClassifierHitEvent,
|
||||
|
|
@ -11,6 +12,8 @@ import type {
|
|||
IslandflowAiRateLimitSnapshot,
|
||||
IslandflowAiReasoningEffort,
|
||||
IslandflowAiTaskKind,
|
||||
IslandflowAiTaskSnapshot,
|
||||
IslandflowAiTaskStatus,
|
||||
OptionFlowFilters,
|
||||
OptionPrint,
|
||||
SmartMoneyEvent,
|
||||
|
|
@ -53,6 +56,55 @@ const formatPercent = (value: number): string => `${Math.round(value)}%`;
|
|||
|
||||
const getTaskStatusLabel = (value: string): string => humanizeValue(value);
|
||||
|
||||
type CopilotTaskCache = Record<string, string>;
|
||||
|
||||
const RUNNING_TASK_STATUSES: IslandflowAiTaskStatus[] = ["queued", "running"];
|
||||
|
||||
export const createCopilotTaskCacheKey = (
|
||||
kind: IslandflowAiTaskKind,
|
||||
contextKey: string,
|
||||
): string => `${kind}:${contextKey}`;
|
||||
|
||||
export const getCachedCopilotTaskId = (
|
||||
cache: CopilotTaskCache,
|
||||
kind: IslandflowAiTaskKind,
|
||||
contextKey: string,
|
||||
): string | null => cache[createCopilotTaskCacheKey(kind, contextKey)] ?? null;
|
||||
|
||||
export const shouldShowCopilotRegenerate = (
|
||||
task: Pick<IslandflowAiTaskSnapshot, "status" | "text" | "compiledScreen"> | null,
|
||||
): boolean =>
|
||||
Boolean(
|
||||
task &&
|
||||
task.status === "completed" &&
|
||||
(task.text.trim().length > 0 || task.compiledScreen),
|
||||
);
|
||||
|
||||
export const isCopilotTaskRunning = (
|
||||
task: Pick<IslandflowAiTaskSnapshot, "status"> | null,
|
||||
): boolean => Boolean(task && RUNNING_TASK_STATUSES.includes(task.status));
|
||||
|
||||
export const getCopilotTaskSurfaceState = (
|
||||
task: Pick<IslandflowAiTaskSnapshot, "status" | "text" | "compiledScreen" | "error"> | null,
|
||||
): "empty" | "running" | "completed" | "failed" | "cancelled" => {
|
||||
if (!task) {
|
||||
return "empty";
|
||||
}
|
||||
if (RUNNING_TASK_STATUSES.includes(task.status)) {
|
||||
return "running";
|
||||
}
|
||||
if (task.status === "completed") {
|
||||
return "completed";
|
||||
}
|
||||
if (task.status === "failed") {
|
||||
return "failed";
|
||||
}
|
||||
return "cancelled";
|
||||
};
|
||||
|
||||
const getSmartMoneyContextKey = (event: SmartMoneyEvent): string =>
|
||||
[event.underlying_id, event.source_ts, event.event_id].join(":");
|
||||
|
||||
type DesktopAiSettingsNotice = {
|
||||
title: string;
|
||||
body: string;
|
||||
|
|
@ -324,19 +376,26 @@ const RateLimitBoard = ({
|
|||
const TaskOutput = ({
|
||||
taskId,
|
||||
emptyMessage,
|
||||
onCancel,
|
||||
}: {
|
||||
taskId: string | null;
|
||||
emptyMessage: string;
|
||||
onCancel?: (taskId: string) => void;
|
||||
}) => {
|
||||
const { state } = useDesktopAi();
|
||||
const task = findTask(state.tasks, taskId);
|
||||
const surfaceState = getCopilotTaskSurfaceState(task);
|
||||
|
||||
if (!task) {
|
||||
return <p className="copilot-empty">{emptyMessage}</p>;
|
||||
return (
|
||||
<div className="copilot-task-output copilot-task-output-empty">
|
||||
<p className="copilot-empty">{emptyMessage}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="copilot-task-output" aria-live="polite">
|
||||
<div className={`copilot-task-output state-${surfaceState}`} aria-live="polite">
|
||||
<div className="copilot-task-head">
|
||||
<div>
|
||||
<strong>{task.title}</strong>
|
||||
|
|
@ -348,8 +407,34 @@ const TaskOutput = ({
|
|||
{getTaskStatusLabel(task.status)}
|
||||
</span>
|
||||
</div>
|
||||
{surfaceState === "running" ? (
|
||||
<div className="copilot-task-running-row">
|
||||
<span className="copilot-spinner" aria-hidden="true" />
|
||||
<p className="copilot-note">
|
||||
{task.status === "queued"
|
||||
? "Queued with the desktop Copilot bridge."
|
||||
: "Generating analysis as deltas arrive."}
|
||||
</p>
|
||||
{onCancel ? (
|
||||
<button
|
||||
className="terminal-button"
|
||||
type="button"
|
||||
onClick={() => onCancel(task.taskId)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{task.error ? <p className="copilot-error">{task.error}</p> : null}
|
||||
{task.text ? <pre className="copilot-task-text">{task.text}</pre> : null}
|
||||
{task.status === "cancelled" && !task.text ? (
|
||||
<p className="copilot-note">This Copilot task was cancelled locally.</p>
|
||||
) : null}
|
||||
{task.text ? (
|
||||
<div className="copilot-markdown">
|
||||
<ReactMarkdown>{task.text}</ReactMarkdown>
|
||||
</div>
|
||||
) : null}
|
||||
{task.compiledScreen ? (
|
||||
<CompiledScreenResult compiled={task.compiledScreen} />
|
||||
) : null}
|
||||
|
|
@ -880,6 +965,8 @@ const SmartMoneyTaskButton = ({
|
|||
symbol,
|
||||
disabled,
|
||||
busyKind,
|
||||
selected,
|
||||
cached,
|
||||
onRun,
|
||||
}: {
|
||||
label: string;
|
||||
|
|
@ -887,17 +974,19 @@ const SmartMoneyTaskButton = ({
|
|||
symbol: string;
|
||||
disabled: boolean;
|
||||
busyKind: IslandflowAiTaskKind | null;
|
||||
selected: boolean;
|
||||
cached: boolean;
|
||||
onRun: (kind: IslandflowAiTaskKind) => void;
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={`terminal-button${kind === "smart-money-explain" ? " terminal-button-primary" : ""}`}
|
||||
className={`terminal-button${kind === "smart-money-explain" ? " terminal-button-primary" : ""}${selected ? " is-active" : ""}`}
|
||||
type="button"
|
||||
onClick={() => onRun(kind)}
|
||||
disabled={busyKind !== null || disabled}
|
||||
title={`${label} for ${symbol}`}
|
||||
>
|
||||
{busyKind === kind ? "Running" : label}
|
||||
{busyKind === kind ? "Running" : cached ? `${label} cached` : label}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -913,10 +1002,17 @@ export function SmartMoneyCopilotPanel({
|
|||
evidencePrints: OptionPrint[];
|
||||
relatedPackets: FlowPacket[];
|
||||
}) {
|
||||
const { bridgeAvailable, shellAvailable, state, runTask } = useDesktopAi();
|
||||
const { bridgeAvailable, shellAvailable, state, runTask, cancelTask } = useDesktopAi();
|
||||
const [busyKind, setBusyKind] = useState<IslandflowAiTaskKind | null>(null);
|
||||
const [selectedKind, setSelectedKind] = useState<IslandflowAiTaskKind>("smart-money-explain");
|
||||
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
||||
const [taskCache, setTaskCache] = useState<CopilotTaskCache>({});
|
||||
const [taskError, setTaskError] = useState<string | null>(null);
|
||||
const contextKey = useMemo(() => getSmartMoneyContextKey(event), [event]);
|
||||
const activeTask = useMemo(
|
||||
() => findTask(state.tasks, activeTaskId),
|
||||
[state.tasks, activeTaskId],
|
||||
);
|
||||
const disabledCopy = requireDesktopActionCopy(
|
||||
shellAvailable,
|
||||
bridgeAvailable,
|
||||
|
|
@ -924,7 +1020,16 @@ export function SmartMoneyCopilotPanel({
|
|||
);
|
||||
const actionsDisabled = !bridgeAvailable || !state.account.loggedIn;
|
||||
|
||||
const handleRun = async (kind: IslandflowAiTaskKind) => {
|
||||
const handleRun = async (kind: IslandflowAiTaskKind, options?: { regenerate?: boolean }) => {
|
||||
setSelectedKind(kind);
|
||||
const cachedTaskId = getCachedCopilotTaskId(taskCache, kind, contextKey);
|
||||
const cachedTask = findTask(state.tasks, cachedTaskId);
|
||||
if (!options?.regenerate && cachedTask) {
|
||||
setActiveTaskId(cachedTask.taskId);
|
||||
setTaskError(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setBusyKind(kind);
|
||||
setTaskError(null);
|
||||
try {
|
||||
|
|
@ -942,6 +1047,10 @@ export function SmartMoneyCopilotPanel({
|
|||
},
|
||||
});
|
||||
setActiveTaskId(result.taskId);
|
||||
setTaskCache((current) => ({
|
||||
...current,
|
||||
[createCopilotTaskCacheKey(kind, contextKey)]: result.taskId,
|
||||
}));
|
||||
} catch (error) {
|
||||
setTaskError(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
|
|
@ -949,6 +1058,17 @@ export function SmartMoneyCopilotPanel({
|
|||
}
|
||||
};
|
||||
|
||||
const handleCancel = async (taskId: string) => {
|
||||
setTaskError(null);
|
||||
try {
|
||||
await cancelTask(taskId);
|
||||
} catch (error) {
|
||||
setTaskError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
};
|
||||
|
||||
const canRegenerate = shouldShowCopilotRegenerate(activeTask);
|
||||
|
||||
return (
|
||||
<div className="copilot-inline-panel">
|
||||
<div className="copilot-inline-head">
|
||||
|
|
@ -970,6 +1090,8 @@ export function SmartMoneyCopilotPanel({
|
|||
symbol={event.underlying_id}
|
||||
disabled={actionsDisabled}
|
||||
busyKind={busyKind}
|
||||
selected={selectedKind === "smart-money-explain"}
|
||||
cached={Boolean(getCachedCopilotTaskId(taskCache, "smart-money-explain", contextKey))}
|
||||
onRun={(kind) => void handleRun(kind)}
|
||||
/>
|
||||
<SmartMoneyTaskButton
|
||||
|
|
@ -978,6 +1100,8 @@ export function SmartMoneyCopilotPanel({
|
|||
symbol={event.underlying_id}
|
||||
disabled={actionsDisabled}
|
||||
busyKind={busyKind}
|
||||
selected={selectedKind === "smart-money-skeptic"}
|
||||
cached={Boolean(getCachedCopilotTaskId(taskCache, "smart-money-skeptic", contextKey))}
|
||||
onRun={(kind) => void handleRun(kind)}
|
||||
/>
|
||||
<SmartMoneyTaskButton
|
||||
|
|
@ -986,6 +1110,8 @@ export function SmartMoneyCopilotPanel({
|
|||
symbol={event.underlying_id}
|
||||
disabled={actionsDisabled}
|
||||
busyKind={busyKind}
|
||||
selected={selectedKind === "smart-money-burst-summary"}
|
||||
cached={Boolean(getCachedCopilotTaskId(taskCache, "smart-money-burst-summary", contextKey))}
|
||||
onRun={(kind) => void handleRun(kind)}
|
||||
/>
|
||||
<SmartMoneyTaskButton
|
||||
|
|
@ -994,14 +1120,29 @@ export function SmartMoneyCopilotPanel({
|
|||
symbol={event.underlying_id}
|
||||
disabled={actionsDisabled}
|
||||
busyKind={busyKind}
|
||||
selected={selectedKind === "watchlist-synthesis"}
|
||||
cached={Boolean(getCachedCopilotTaskId(taskCache, "watchlist-synthesis", contextKey))}
|
||||
onRun={(kind) => void handleRun(kind)}
|
||||
/>
|
||||
</div>
|
||||
{canRegenerate ? (
|
||||
<div className="copilot-apply-row">
|
||||
<button
|
||||
className="terminal-button"
|
||||
type="button"
|
||||
onClick={() => void handleRun(selectedKind, { regenerate: true })}
|
||||
disabled={actionsDisabled || busyKind !== null}
|
||||
>
|
||||
Regenerate
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{disabledCopy ? <p className="copilot-note">{disabledCopy}</p> : null}
|
||||
{taskError ? <p className="copilot-error">{taskError}</p> : null}
|
||||
<TaskOutput
|
||||
taskId={activeTaskId}
|
||||
emptyMessage="Run an explanation, skepticism pass, burst summary, or watchlist synthesis to see the result here."
|
||||
onCancel={(taskId) => void handleCancel(taskId)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1024,10 +1165,14 @@ export function ReplayCopilotPanel({
|
|||
flowPackets: FlowPacket[];
|
||||
optionPrints: OptionPrint[];
|
||||
}) {
|
||||
const { bridgeAvailable, shellAvailable, state, runTask } = useDesktopAi();
|
||||
const { bridgeAvailable, shellAvailable, state, runTask, cancelTask } = useDesktopAi();
|
||||
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [taskError, setTaskError] = useState<string | null>(null);
|
||||
const activeTask = useMemo(
|
||||
() => findTask(state.tasks, activeTaskId),
|
||||
[state.tasks, activeTaskId],
|
||||
);
|
||||
const disabledCopy = requireDesktopActionCopy(
|
||||
shellAvailable,
|
||||
bridgeAvailable,
|
||||
|
|
@ -1059,6 +1204,17 @@ export function ReplayCopilotPanel({
|
|||
}
|
||||
};
|
||||
|
||||
const handleCancel = async (taskId: string) => {
|
||||
setTaskError(null);
|
||||
try {
|
||||
await cancelTask(taskId);
|
||||
} catch (error) {
|
||||
setTaskError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
};
|
||||
|
||||
const canRegenerate = shouldShowCopilotRegenerate(activeTask);
|
||||
|
||||
return (
|
||||
<CopilotPane
|
||||
title="Replay postmortem"
|
||||
|
|
@ -1074,7 +1230,7 @@ export function ReplayCopilotPanel({
|
|||
onClick={() => void handleRun()}
|
||||
disabled={actionsDisabled}
|
||||
>
|
||||
{busy ? "Running" : "Generate postmortem"}
|
||||
{busy ? "Running" : canRegenerate ? "Regenerate" : "Generate postmortem"}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
|
@ -1088,6 +1244,7 @@ export function ReplayCopilotPanel({
|
|||
<TaskOutput
|
||||
taskId={activeTaskId}
|
||||
emptyMessage="Generate a replay postmortem to capture the cleanest read from the current session slice."
|
||||
onCancel={(taskId) => void handleCancel(taskId)}
|
||||
/>
|
||||
</CopilotPane>
|
||||
);
|
||||
|
|
@ -1100,7 +1257,7 @@ export function ScreenCompilerPanel({
|
|||
currentFilters: OptionFlowFilters;
|
||||
onApplyFilters: (next: OptionFlowFilters) => void;
|
||||
}) {
|
||||
const { bridgeAvailable, shellAvailable, state, runTask } = useDesktopAi();
|
||||
const { bridgeAvailable, shellAvailable, state, runTask, cancelTask } = useDesktopAi();
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
|
@ -1141,7 +1298,17 @@ export function ScreenCompilerPanel({
|
|||
}
|
||||
};
|
||||
|
||||
const handleCancel = async (taskId: string) => {
|
||||
setTaskError(null);
|
||||
try {
|
||||
await cancelTask(taskId);
|
||||
} catch (error) {
|
||||
setTaskError(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
};
|
||||
|
||||
const compiledFilters = activeTask?.compiledScreen?.compiledFilters ?? null;
|
||||
const canRegenerate = shouldShowCopilotRegenerate(activeTask);
|
||||
|
||||
return (
|
||||
<CopilotPane
|
||||
|
|
@ -1158,7 +1325,7 @@ export function ScreenCompilerPanel({
|
|||
onClick={() => void handleCompile()}
|
||||
disabled={actionsDisabled}
|
||||
>
|
||||
{busy ? "Compiling" : "Compile screen"}
|
||||
{busy ? "Compiling" : canRegenerate ? "Regenerate" : "Compile screen"}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
|
|
@ -1197,6 +1364,7 @@ export function ScreenCompilerPanel({
|
|||
<TaskOutput
|
||||
taskId={activeTaskId}
|
||||
emptyMessage="Compile a natural-language screen to preview the translated filter set and rationale."
|
||||
onCancel={(taskId) => void handleCancel(taskId)}
|
||||
/>
|
||||
</CopilotPane>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,11 @@ import {
|
|||
getDesktopAiModelSelectLabel,
|
||||
getDesktopAiProfileBadgeLabel,
|
||||
getDesktopAiSettingsBridgeNotice,
|
||||
createCopilotTaskCacheKey,
|
||||
getCachedCopilotTaskId,
|
||||
getCopilotTaskSurfaceState,
|
||||
requireDesktopActionCopy,
|
||||
shouldShowCopilotRegenerate,
|
||||
} from "./desktop-ai-panels";
|
||||
|
||||
describe("desktop ai runtime detection", () => {
|
||||
|
|
@ -41,6 +45,7 @@ describe("desktop ai runtime detection", () => {
|
|||
logout: async () => {},
|
||||
updatePreferences: async () => {},
|
||||
runTask: async () => ({ taskId: "task-1" }),
|
||||
cancelTask: async () => {},
|
||||
subscribe: () => () => {},
|
||||
},
|
||||
},
|
||||
|
|
@ -146,3 +151,49 @@ describe("desktop ai settings copy", () => {
|
|||
).toBe("Selected");
|
||||
});
|
||||
});
|
||||
|
||||
describe("desktop ai panel task helpers", () => {
|
||||
it("looks up cached task ids by action kind and context key", () => {
|
||||
const key = createCopilotTaskCacheKey("smart-money-explain", "AAPL:1:event-1");
|
||||
|
||||
expect(key).toBe("smart-money-explain:AAPL:1:event-1");
|
||||
expect(
|
||||
getCachedCopilotTaskId(
|
||||
{ [key]: "task-1" },
|
||||
"smart-money-explain",
|
||||
"AAPL:1:event-1",
|
||||
),
|
||||
).toBe("task-1");
|
||||
});
|
||||
|
||||
it("only shows regenerate once a completed result has output", () => {
|
||||
expect(
|
||||
shouldShowCopilotRegenerate({
|
||||
status: "completed",
|
||||
text: "## Thesis",
|
||||
compiledScreen: null,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldShowCopilotRegenerate({
|
||||
status: "running",
|
||||
text: "partial",
|
||||
compiledScreen: null,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(shouldShowCopilotRegenerate(null)).toBe(false);
|
||||
});
|
||||
|
||||
it("maps queued, running, failed, and cancelled tasks to result surface states", () => {
|
||||
expect(getCopilotTaskSurfaceState(null)).toBe("empty");
|
||||
expect(getCopilotTaskSurfaceState({ status: "queued", text: "", compiledScreen: null, error: null })).toBe(
|
||||
"running",
|
||||
);
|
||||
expect(getCopilotTaskSurfaceState({ status: "running", text: "", compiledScreen: null, error: null })).toBe(
|
||||
"running",
|
||||
);
|
||||
expect(getCopilotTaskSurfaceState({ status: "cancelled", text: "", compiledScreen: null, error: null })).toBe(
|
||||
"cancelled",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ type DesktopAiBridge = {
|
|||
next: Partial<{ model: string | null; reasoningEffort: IslandflowAiReasoningEffort | null }>
|
||||
) => Promise<void>;
|
||||
runTask: (request: IslandflowAiTaskRequest) => Promise<{ taskId: string }>;
|
||||
cancelTask: (taskId: string) => Promise<void>;
|
||||
subscribe: (listener: (state: IslandflowAiState) => void) => () => void;
|
||||
};
|
||||
};
|
||||
|
|
@ -53,6 +54,7 @@ type DesktopAiContextValue = {
|
|||
next: Partial<{ model: string | null; reasoningEffort: IslandflowAiReasoningEffort | null }>
|
||||
) => Promise<void>;
|
||||
runTask: (request: IslandflowAiTaskRequest) => Promise<{ taskId: string }>;
|
||||
cancelTask: (taskId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
const BRIDGE_POLL_INTERVAL_MS = 250;
|
||||
|
|
@ -261,7 +263,8 @@ export function DesktopAiProvider({ children }: { children: ReactNode }) {
|
|||
cancelLogin: bridge?.ai.cancelLogin ?? rejectDesktopOnly,
|
||||
logout: bridge?.ai.logout ?? rejectDesktopOnly,
|
||||
updatePreferences: bridge?.ai.updatePreferences ?? rejectDesktopOnly,
|
||||
runTask: bridge?.ai.runTask ?? rejectDesktopOnly
|
||||
runTask: bridge?.ai.runTask ?? rejectDesktopOnly,
|
||||
cancelTask: bridge?.ai.cancelTask ?? rejectDesktopOnly
|
||||
}),
|
||||
[bridge, shellAvailable, state]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1182,6 +1182,7 @@ h3 {
|
|||
.copilot-empty,
|
||||
.copilot-device-code,
|
||||
.copilot-task-text,
|
||||
.copilot-markdown,
|
||||
.copilot-json-block {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -1317,6 +1318,45 @@ h3 {
|
|||
background: oklch(0.12 0.01 250 / 0.72);
|
||||
}
|
||||
|
||||
.copilot-task-output-empty {
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.copilot-task-running-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid oklch(0.78 0.12 74 / 0.24);
|
||||
border-radius: 10px;
|
||||
background: oklch(0.78 0.12 74 / 0.06);
|
||||
}
|
||||
|
||||
.copilot-task-running-row .terminal-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.copilot-spinner {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 999px;
|
||||
border: 2px solid oklch(0.88 0.06 76 / 0.3);
|
||||
border-top-color: oklch(0.88 0.06 76);
|
||||
animation: copilot-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes copilot-spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.terminal-button.is-active {
|
||||
border-color: var(--border-strong);
|
||||
box-shadow: 0 0 0 1px oklch(0.78 0.12 74 / 0.22);
|
||||
}
|
||||
|
||||
.copilot-task-text,
|
||||
.copilot-json-block,
|
||||
.copilot-device-code {
|
||||
|
|
@ -1329,6 +1369,78 @@ h3 {
|
|||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.copilot-markdown {
|
||||
padding: 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
background: oklch(0.1 0.009 250 / 0.92);
|
||||
color: var(--text);
|
||||
font-family: var(--font-sans), sans-serif;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.copilot-markdown > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.copilot-markdown > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.copilot-markdown h1,
|
||||
.copilot-markdown h2,
|
||||
.copilot-markdown h3,
|
||||
.copilot-markdown h4 {
|
||||
margin: 1.1em 0 0.45em;
|
||||
color: var(--text);
|
||||
font-family: var(--font-display), sans-serif;
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
.copilot-markdown p,
|
||||
.copilot-markdown ul,
|
||||
.copilot-markdown ol,
|
||||
.copilot-markdown blockquote,
|
||||
.copilot-markdown pre {
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
|
||||
.copilot-markdown ul,
|
||||
.copilot-markdown ol {
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.copilot-markdown li + li {
|
||||
margin-top: 0.35em;
|
||||
}
|
||||
|
||||
.copilot-markdown code {
|
||||
font-family: var(--font-mono), monospace;
|
||||
font-size: 0.92em;
|
||||
color: oklch(0.88 0.06 76);
|
||||
}
|
||||
|
||||
.copilot-markdown :not(pre) > code {
|
||||
padding: 0.1rem 0.32rem;
|
||||
border-radius: 6px;
|
||||
background: oklch(0.97 0.008 250 / 0.08);
|
||||
border: 1px solid oklch(0.72 0.012 250 / 0.14);
|
||||
}
|
||||
|
||||
.copilot-markdown pre {
|
||||
overflow-x: auto;
|
||||
padding: 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
background: oklch(0.07 0.008 250 / 0.9);
|
||||
}
|
||||
|
||||
.copilot-markdown blockquote {
|
||||
padding-left: 12px;
|
||||
border-left: 3px solid var(--accent);
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.copilot-device-code {
|
||||
font-size: clamp(1.3rem, 2vw, 1.7rem);
|
||||
letter-spacing: 0.18em;
|
||||
|
|
|
|||
3
apps/web/next-env.d.ts
vendored
3
apps/web/next-env.d.ts
vendored
|
|
@ -1,5 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@
|
|||
"lightweight-charts": "^4.2.0",
|
||||
"next": "^16.2.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0"
|
||||
"react-dom": "^19.2.0",
|
||||
"react-markdown": "^10.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.10",
|
||||
|
|
|
|||
159
bun.lock
159
bun.lock
|
|
@ -32,6 +32,7 @@
|
|||
"next": "^16.2.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.10",
|
||||
|
|
@ -401,18 +402,28 @@
|
|||
|
||||
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
|
||||
|
||||
"@types/eslint": ["@types/eslint@9.6.1", "", { "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag=="],
|
||||
|
||||
"@types/eslint-scope": ["@types/eslint-scope@3.7.7", "", { "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
|
||||
"@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="],
|
||||
|
||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/mute-stream": ["@types/mute-stream@0.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow=="],
|
||||
|
||||
"@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
|
||||
|
|
@ -423,10 +434,14 @@
|
|||
|
||||
"@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="],
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"@types/wrap-ansi": ["@types/wrap-ansi@3.0.0", "", {}, "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g=="],
|
||||
|
||||
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="],
|
||||
|
||||
"@vscode/sudo-prompt": ["@vscode/sudo-prompt@9.3.2", "", {}, "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw=="],
|
||||
|
||||
"@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="],
|
||||
|
|
@ -493,6 +508,8 @@
|
|||
|
||||
"author-regex": ["author-regex@1.0.0", "", {}, "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
|
@ -525,8 +542,18 @@
|
|||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
||||
|
||||
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
||||
|
||||
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||
|
||||
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
||||
|
||||
"chardet": ["chardet@0.7.0", "", {}, "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="],
|
||||
|
||||
"chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="],
|
||||
|
|
@ -559,6 +586,8 @@
|
|||
|
||||
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
|
||||
|
||||
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||
|
||||
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||
|
||||
"compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="],
|
||||
|
|
@ -575,6 +604,8 @@
|
|||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
||||
|
||||
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
||||
|
||||
"defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
|
||||
|
|
@ -585,10 +616,14 @@
|
|||
|
||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="],
|
||||
|
||||
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
|
||||
|
|
@ -629,6 +664,8 @@
|
|||
|
||||
"estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="],
|
||||
|
||||
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||
|
||||
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
|
@ -637,6 +674,8 @@
|
|||
|
||||
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"external-editor": ["external-editor@3.1.0", "", { "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew=="],
|
||||
|
||||
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
|
||||
|
|
@ -703,8 +742,14 @@
|
|||
|
||||
"hasown": ["hasown@2.0.3", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="],
|
||||
|
||||
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
||||
|
||||
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||
|
||||
"hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="],
|
||||
|
||||
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
||||
|
||||
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
|
||||
|
||||
"http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="],
|
||||
|
|
@ -731,26 +776,38 @@
|
|||
|
||||
"ini": ["ini@2.0.0", "", {}, "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA=="],
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||
|
||||
"interpret": ["interpret@3.1.1", "", {}, "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ=="],
|
||||
|
||||
"ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="],
|
||||
|
||||
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
|
||||
|
||||
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
|
||||
|
||||
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="],
|
||||
|
||||
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
|
||||
|
||||
"is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="],
|
||||
|
||||
"is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||
|
||||
"is-stream": ["is-stream@1.1.0", "", {}, "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ=="],
|
||||
|
||||
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
|
||||
|
|
@ -793,6 +850,8 @@
|
|||
|
||||
"log-update": ["log-update@5.0.1", "", { "dependencies": { "ansi-escapes": "^5.0.0", "cli-cursor": "^4.0.0", "slice-ansi": "^5.0.0", "strip-ansi": "^7.0.1", "wrap-ansi": "^8.0.1" } }, "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
|
||||
"lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
|
||||
|
||||
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
|
||||
|
|
@ -803,12 +862,70 @@
|
|||
|
||||
"matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
|
||||
|
||||
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="],
|
||||
|
||||
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
|
||||
|
||||
"mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
|
||||
|
||||
"mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
|
||||
|
||||
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
|
||||
|
||||
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
|
||||
|
||||
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
|
||||
|
||||
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
|
||||
|
||||
"mem": ["mem@4.3.0", "", { "dependencies": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", "p-is-promise": "^2.0.0" } }, "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
|
||||
|
||||
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
|
||||
|
||||
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
|
||||
|
||||
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
|
||||
|
||||
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
|
||||
|
||||
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
|
||||
|
||||
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
||||
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
|
||||
|
||||
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
|
||||
|
||||
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
|
||||
|
||||
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
|
||||
|
||||
"micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
|
||||
|
||||
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||
|
||||
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
|
||||
|
||||
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
|
||||
|
||||
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
|
||||
|
||||
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
||||
|
||||
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
|
||||
|
||||
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
||||
|
||||
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
|
@ -899,6 +1016,8 @@
|
|||
|
||||
"parse-author": ["parse-author@2.0.0", "", { "dependencies": { "author-regex": "^1.0.0" } }, "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw=="],
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"parse-json": ["parse-json@2.2.0", "", { "dependencies": { "error-ex": "^1.2.0" } }, "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
|
@ -937,6 +1056,8 @@
|
|||
|
||||
"promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
|
||||
|
||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
|
@ -947,6 +1068,8 @@
|
|||
|
||||
"react-dom": ["react-dom@19.2.6", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.6" } }, "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g=="],
|
||||
|
||||
"react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="],
|
||||
|
||||
"read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="],
|
||||
|
||||
"read-pkg": ["read-pkg@2.0.0", "", { "dependencies": { "load-json-file": "^2.0.0", "normalize-package-data": "^2.3.2", "path-type": "^2.0.0" } }, "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA=="],
|
||||
|
|
@ -959,6 +1082,10 @@
|
|||
|
||||
"redis": ["redis@5.10.0", "", { "dependencies": { "@redis/bloom": "5.10.0", "@redis/client": "5.10.0", "@redis/json": "5.10.0", "@redis/search": "5.10.0", "@redis/time-series": "5.10.0" } }, "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw=="],
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
|
@ -1021,6 +1148,8 @@
|
|||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
"spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="],
|
||||
|
||||
"spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
|
||||
|
|
@ -1037,6 +1166,8 @@
|
|||
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
|
@ -1045,6 +1176,10 @@
|
|||
|
||||
"strip-outer": ["strip-outer@1.0.1", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg=="],
|
||||
|
||||
"style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="],
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||
|
||||
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
|
||||
|
||||
"sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],
|
||||
|
|
@ -1067,8 +1202,12 @@
|
|||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"trim-repeated": ["trim-repeated@1.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.2" } }, "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"tweetnacl": ["tweetnacl@1.0.3", "", {}, "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="],
|
||||
|
|
@ -1081,10 +1220,22 @@
|
|||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unique-filename": ["unique-filename@2.0.1", "", { "dependencies": { "unique-slug": "^3.0.0" } }, "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A=="],
|
||||
|
||||
"unique-slug": ["unique-slug@3.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||
|
||||
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||
|
||||
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||
|
||||
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||
|
||||
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
|
@ -1095,6 +1246,10 @@
|
|||
|
||||
"validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="],
|
||||
|
||||
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
|
||||
|
|
@ -1133,6 +1288,8 @@
|
|||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@electron-forge/template-webpack-typescript/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
|
||||
|
||||
"@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="],
|
||||
|
|
@ -1243,6 +1400,8 @@
|
|||
|
||||
"ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
|
||||
"postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
|
||||
|
||||
"read-pkg-up/find-up": ["find-up@2.1.0", "", { "dependencies": { "locate-path": "^2.0.0" } }, "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ=="],
|
||||
|
|
|
|||
305
docs/turns/2026-05-20-ai-alert-copilot-ux.html
Normal file
305
docs/turns/2026-05-20-ai-alert-copilot-ux.html
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>2026-05-20 · AI Alert Copilot UX</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: oklch(0.11 0.01 250);
|
||||
--panel: oklch(0.16 0.013 250 / 0.94);
|
||||
--panel-2: oklch(0.13 0.012 250 / 0.92);
|
||||
--text: oklch(0.93 0.014 250);
|
||||
--muted: oklch(0.74 0.018 250);
|
||||
--faint: oklch(0.6 0.016 250);
|
||||
--line: oklch(0.75 0.014 250 / 0.14);
|
||||
--accent: oklch(0.79 0.12 74);
|
||||
--accent-soft: oklch(0.79 0.12 74 / 0.12);
|
||||
--green-soft: oklch(0.75 0.12 151 / 0.12);
|
||||
--red-soft: oklch(0.72 0.14 28 / 0.12);
|
||||
--shadow: 0 24px 80px oklch(0.02 0.01 250 / 0.42);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at top left, oklch(0.8 0.12 74 / 0.08), transparent 30%),
|
||||
linear-gradient(180deg, oklch(0.15 0.012 250), var(--bg));
|
||||
color: var(--text);
|
||||
}
|
||||
main {
|
||||
width: min(1160px, calc(100vw - 40px));
|
||||
margin: 0 auto;
|
||||
padding: 36px 0 64px;
|
||||
}
|
||||
h1, h2, h3, .eyebrow, .meta, code, pre {
|
||||
font-family: "IBM Plex Mono", ui-monospace, monospace;
|
||||
}
|
||||
h1, h2, h3 { margin: 0; }
|
||||
p, li { color: var(--muted); line-height: 1.7; }
|
||||
a { color: var(--accent); }
|
||||
main > * + * { margin-top: 22px; }
|
||||
.hero, section {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 24px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.hero {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
padding: 28px;
|
||||
background: linear-gradient(135deg, oklch(0.2 0.017 250 / 0.96), oklch(0.14 0.012 250 / 0.98));
|
||||
}
|
||||
.eyebrow {
|
||||
margin: 0 0 10px;
|
||||
color: var(--accent);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.18em;
|
||||
font-size: 0.76rem;
|
||||
}
|
||||
.hero h1 {
|
||||
font-size: clamp(2rem, 3.4vw, 3.5rem);
|
||||
line-height: 0.96;
|
||||
letter-spacing: 0.03em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.hero-copy { max-width: 76ch; margin: 14px 0 0; }
|
||||
.hero-grid, .two-col, .validation-grid {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
.hero-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.two-col { grid-template-columns: minmax(0, 1.08fr) minmax(320px, 0.92fr); }
|
||||
.validation-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.stat, .validation-card, .callout {
|
||||
padding: 16px 18px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--line);
|
||||
background: oklch(0.12 0.01 250 / 0.5);
|
||||
}
|
||||
.stat span, .validation-card span {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: var(--faint);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.16em;
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.validation-card.good { background: var(--green-soft); }
|
||||
.validation-card.warn { background: var(--red-soft); }
|
||||
.callout { background: var(--accent-soft); }
|
||||
section {
|
||||
padding: 24px;
|
||||
background: var(--panel-2);
|
||||
}
|
||||
section h2 {
|
||||
margin-bottom: 14px;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
ul { margin: 0; padding-left: 20px; }
|
||||
li + li { margin-top: 10px; }
|
||||
.chip-row { display: flex; flex-wrap: wrap; gap: 8px; }
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 30px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid oklch(0.8 0.12 74 / 0.28);
|
||||
background: var(--accent-soft);
|
||||
font-size: 0.74rem;
|
||||
}
|
||||
.meta { color: var(--faint); font-size: 0.82rem; }
|
||||
pre.diff {
|
||||
margin: 0;
|
||||
padding: 16px 18px;
|
||||
overflow: auto;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--line);
|
||||
background: oklch(0.08 0.008 250 / 0.95);
|
||||
color: var(--text);
|
||||
line-height: 1.5;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.diff-title {
|
||||
margin: 18px 0 10px;
|
||||
color: var(--faint);
|
||||
font-size: 0.76rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@media (max-width: 860px) {
|
||||
main { width: min(100vw - 24px, 1160px); padding-top: 18px; }
|
||||
.hero-grid, .two-col, .validation-grid { grid-template-columns: minmax(0, 1fr); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<article class="hero">
|
||||
<div>
|
||||
<p class="eyebrow">Repository Turn Document</p>
|
||||
<h1>AI Alert Copilot UX</h1>
|
||||
<p class="hero-copy">
|
||||
Upgraded the desktop Copilot result surface so generated analysis now reads like Islandflow content instead of raw terminal output. The alert panel can reuse session results by action, expose Regenerate after completion, show live running/cancel states, and request task cancellation through the desktop bridge.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero-grid">
|
||||
<div class="stat"><span>Issue</span><strong>islandflow-xtg</strong></div>
|
||||
<div class="stat"><span>Primary Surface</span><strong>Alert Copilot</strong></div>
|
||||
<div class="stat"><span>Validation</span><strong>272 tests passed</strong></div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<section>
|
||||
<h2>Summary</h2>
|
||||
<p>
|
||||
Implemented the AI alert Copilot UX refinement: markdown rendering, in-place loading and cancellation, current-session task caching for alert actions, and regenerate controls for completed outputs.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="two-col">
|
||||
<div>
|
||||
<h2>Changes Made</h2>
|
||||
<ul>
|
||||
<li>Added <code>react-markdown</code> to the web app and replaced raw Copilot result <code>pre</code> output with safe markdown rendering.</li>
|
||||
<li>Created reusable task-surface helpers for empty, running, completed, failed, and cancelled display decisions.</li>
|
||||
<li>Added a compact spinner, status copy, and Cancel button for queued/running task states.</li>
|
||||
<li>Added per-session alert task caching keyed by action kind and smart-money event context.</li>
|
||||
<li>Added Regenerate for completed alert, replay, and screen compiler results.</li>
|
||||
<li>Extended the desktop AI bridge, preload script, IPC constants, main process handler, and desktop service with <code>cancelTask(taskId)</code>.</li>
|
||||
<li>Added tests for cache selection, regenerate eligibility, task-surface states, and desktop cancellation behavior.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Context</h2>
|
||||
<p>
|
||||
The previous Copilot output surface showed model text as raw preformatted content. That made structured markdown harder to scan and gave users no way to interrupt a task once it started.
|
||||
</p>
|
||||
<p>
|
||||
The plan treated persistence as current app-session persistence only, so the cache intentionally lives in panel state rather than disk or a global store.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Important Implementation Details</h2>
|
||||
<ul>
|
||||
<li>Markdown is rendered through <code>ReactMarkdown</code> without raw HTML support, so embedded HTML is not executed as markup.</li>
|
||||
<li>Alert action cache keys combine <code>IslandflowAiTaskKind</code> with <code>underlying_id</code>, <code>source_ts</code>, and <code>event_id</code>.</li>
|
||||
<li>Completed cached alert actions select the existing task instead of starting a new run. Regenerate intentionally replaces the cached task id for that action.</li>
|
||||
<li>Cancellation marks the task <code>cancelled</code> locally, removes its active thread mapping, ignores later deltas/completion, and best-effort calls <code>turn/cancel</code> on the app-server.</li>
|
||||
<li>Replay and screen compiler panels reuse the improved result surface, cancellation, and regenerate wording where they already share task output behavior.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Relevant Diff Snippets</h2>
|
||||
<p class="meta">
|
||||
Patch-style snippets prepared for Diffs-style review. Reference: <a href="https://diffs.com/docs">Diffs rendering docs</a>.
|
||||
</p>
|
||||
<p class="diff-title">Task result surface</p>
|
||||
<pre class="diff" data-diffs="patch"><code>@@ apps/web/app/desktop-ai-panels.tsx
|
||||
+import ReactMarkdown from "react-markdown";
|
||||
+
|
||||
+export const getCopilotTaskSurfaceState = (...) => {
|
||||
+ if (!task) return "empty";
|
||||
+ if (RUNNING_TASK_STATUSES.includes(task.status)) return "running";
|
||||
+ if (task.status === "completed") return "completed";
|
||||
+ if (task.status === "failed") return "failed";
|
||||
+ return "cancelled";
|
||||
+};
|
||||
+
|
||||
+{surfaceState === "running" ? (
|
||||
+ <div className="copilot-task-running-row">
|
||||
+ <span className="copilot-spinner" aria-hidden="true" />
|
||||
+ <p className="copilot-note">Generating analysis as deltas arrive.</p>
|
||||
+ <button className="terminal-button" onClick={() => onCancel(task.taskId)}>Cancel</button>
|
||||
+ </div>
|
||||
+) : null}
|
||||
+{task.text ? (
|
||||
+ <div className="copilot-markdown">
|
||||
+ <ReactMarkdown>{task.text}</ReactMarkdown>
|
||||
+ </div>
|
||||
+) : null}</code></pre>
|
||||
|
||||
<p class="diff-title">Desktop cancellation bridge</p>
|
||||
<pre class="diff" data-diffs="patch"><code>@@ apps/desktop/src/desktop-ai-ipc.ts
|
||||
+export const DESKTOP_AI_CANCEL_TASK = "islandflow:desktop-ai:cancel-task";
|
||||
|
||||
@@ apps/desktop/src/main.ts
|
||||
+ipcMain.handle(DESKTOP_AI_CANCEL_TASK, async (event, taskId) => {
|
||||
+ guard(event);
|
||||
+ await service.cancelTask(String(taskId));
|
||||
+});
|
||||
|
||||
@@ apps/desktop/src/desktop-ai.ts
|
||||
+async cancelTask(taskId: string): Promise<void> {
|
||||
+ const task = this.state.tasks.find((candidate) => candidate.taskId === taskId);
|
||||
+ if (!task || (task.status !== "queued" && task.status !== "running")) return;
|
||||
+ this.locallyCancelledTaskIds.add(taskId);
|
||||
+ this.patchTask(taskId, { status: "cancelled", error: null });
|
||||
+ if (task.threadId) {
|
||||
+ this.activeTasksByThreadId.delete(task.threadId);
|
||||
+ await this.client.request("turn/cancel", { threadId: task.threadId, turnId: task.turnId ?? undefined }).catch(() => undefined);
|
||||
+ }
|
||||
+}</code></pre>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Expected Impact for End-Users</h2>
|
||||
<ul>
|
||||
<li>Generated Copilot analysis is easier to read because headings, lists, code, and inline emphasis render with Islandflow typography.</li>
|
||||
<li>Users get immediate feedback while a task is queued or running, instead of an empty-looking panel.</li>
|
||||
<li>Users can cancel a running Copilot task from the same surface where the output appears.</li>
|
||||
<li>Switching between alert AI actions no longer discards completed outputs during the same app session.</li>
|
||||
<li>Regenerate makes fresh model runs deliberate rather than accidental.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Validation</h2>
|
||||
<div class="validation-grid">
|
||||
<div class="validation-card good">
|
||||
<span>Passed</span>
|
||||
<strong><code>bun test</code></strong>
|
||||
<p>All 272 tests passed across 43 files.</p>
|
||||
</div>
|
||||
<div class="validation-card good">
|
||||
<span>Passed</span>
|
||||
<strong><code>bun test apps/web/app/desktop-ai.test.ts apps/desktop/src/desktop-ai.test.ts</code></strong>
|
||||
<p>23 focused desktop AI tests passed after the final helper adjustment.</p>
|
||||
</div>
|
||||
<div class="validation-card warn">
|
||||
<span>Blocked</span>
|
||||
<strong><code>bun --cwd=apps/web run build</code></strong>
|
||||
<p>The production build compiled successfully, then failed during TypeScript on the known <code>packages/types/src/desktop-ai.ts</code> import-extension issue: <code>./events.ts</code> requires <code>allowImportingTsExtensions</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Issues, Limitations, and Mitigations</h2>
|
||||
<ul>
|
||||
<li>The app-server cancellation protocol is treated as best-effort. If <code>turn/cancel</code> is unsupported, Islandflow still marks the task cancelled locally and ignores late output.</li>
|
||||
<li>Session result caching is intentionally in-memory panel state only. It does not survive a desktop restart or full page reload.</li>
|
||||
<li>The web build remains blocked by the existing shared-types TypeScript extension configuration issue, not by this Copilot UX implementation.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Follow-up Work</h2>
|
||||
<ul>
|
||||
<li>Consider a dedicated Beads issue to resolve the shared-types <code>.ts</code> import extension build blocker.</li>
|
||||
<li>Consider moving task caches into a small shared hook if replay and screen compiler need context-key reuse beyond the active result.</li>
|
||||
<li>Consider adding visual browser coverage for markdown-heavy Copilot output once a stable desktop/web test harness is available.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue