@korajs/cli 1.0.0-beta.12 → 1.0.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -107
- package/dist/bin.cjs +808 -340
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +588 -174
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-G2PBYMMA.js → chunk-7Y4J2GXP.js} +168 -153
- package/dist/chunk-7Y4J2GXP.js.map +1 -0
- package/dist/{chunk-Z7GCXMGW.js → chunk-TIKGLNER.js} +44 -9
- package/dist/chunk-TIKGLNER.js.map +1 -0
- package/dist/{chunk-4IKCHRJ6.js → chunk-TVIRMDER.js} +122 -112
- package/dist/chunk-TVIRMDER.js.map +1 -0
- package/dist/create.cjs +63 -28
- package/dist/create.cjs.map +1 -1
- package/dist/create.js +2 -2
- package/dist/index.cjs +41 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +8 -2
- package/dist/{studio-server-NIFKZI4F.js → studio-server-XLGZNGG5.js} +11 -2
- package/dist/studio-server-XLGZNGG5.js.map +1 -0
- package/dist/vite.cjs +351 -0
- package/dist/vite.cjs.map +1 -0
- package/dist/vite.d.cts +161 -0
- package/dist/vite.d.ts +161 -0
- package/dist/vite.js +321 -0
- package/dist/vite.js.map +1 -0
- package/package.json +18 -8
- package/templates/react-basic/AGENTS.md +5 -5
- package/templates/react-basic/vite.config.ts +4 -1
- package/templates/react-sync/AGENTS.md +5 -5
- package/templates/react-sync/server.ts +8 -0
- package/templates/react-sync/src/vite-env.d.ts +1 -0
- package/templates/react-sync/vite.config.ts +4 -1
- package/templates/react-tailwind/AGENTS.md +5 -5
- package/templates/react-tailwind/vite.config.ts +10 -1
- package/templates/react-tailwind-sync/AGENTS.md +9 -7
- package/templates/react-tailwind-sync/server.ts +8 -0
- package/templates/react-tailwind-sync/src/App.tsx +1 -1
- package/templates/react-tailwind-sync/src/kora.ts +32 -0
- package/templates/react-tailwind-sync/src/main.tsx +1 -23
- package/templates/react-tailwind-sync/src/modules/todos/todo.mutations.ts +4 -8
- package/templates/react-tailwind-sync/src/modules/todos/todo.queries.ts +2 -2
- package/templates/react-tailwind-sync/src/modules/todos/useTodos.ts +1 -1
- package/templates/react-tailwind-sync/src/vite-env.d.ts +1 -0
- package/templates/react-tailwind-sync/vite.config.ts +10 -1
- package/templates/svelte-basic/AGENTS.md +4 -4
- package/templates/svelte-basic/package.json.hbs +6 -0
- package/templates/svelte-basic/src/App.svelte +2 -1
- package/templates/svelte-basic/vite.config.ts +4 -1
- package/templates/svelte-sync/AGENTS.md +4 -4
- package/templates/svelte-sync/server.ts +8 -0
- package/templates/svelte-sync/src/App.svelte +2 -1
- package/templates/svelte-sync/vite.config.ts +4 -1
- package/templates/svelte-tailwind/AGENTS.md +4 -4
- package/templates/svelte-tailwind/package.json.hbs +7 -1
- package/templates/svelte-tailwind/src/App.svelte +2 -1
- package/templates/svelte-tailwind/vite.config.ts +10 -1
- package/templates/svelte-tailwind-sync/AGENTS.md +4 -4
- package/templates/svelte-tailwind-sync/package.json.hbs +1 -1
- package/templates/svelte-tailwind-sync/server.ts +8 -0
- package/templates/svelte-tailwind-sync/src/App.svelte +2 -1
- package/templates/svelte-tailwind-sync/vite.config.ts +10 -1
- package/templates/tauri-react/AGENTS.md +5 -5
- package/templates/tauri-react/package.json.hbs +1 -1
- package/templates/tauri-react/server.ts +8 -0
- package/templates/tauri-react/src/vite-env.d.ts +1 -0
- package/templates/vue-basic/AGENTS.md +4 -4
- package/templates/vue-basic/package.json.hbs +6 -0
- package/templates/vue-basic/vite.config.ts +4 -1
- package/templates/vue-sync/AGENTS.md +4 -4
- package/templates/vue-sync/server.ts +8 -0
- package/templates/vue-sync/vite.config.ts +4 -1
- package/templates/vue-tailwind/AGENTS.md +4 -4
- package/templates/vue-tailwind/package.json.hbs +6 -0
- package/templates/vue-tailwind/vite.config.ts +4 -1
- package/templates/vue-tailwind-sync/AGENTS.md +4 -4
- package/templates/vue-tailwind-sync/server.ts +8 -0
- package/templates/vue-tailwind-sync/vite.config.ts +4 -1
- package/dist/chunk-4IKCHRJ6.js.map +0 -1
- package/dist/chunk-G2PBYMMA.js.map +0 -1
- package/dist/chunk-Z7GCXMGW.js.map +0 -1
- package/dist/studio-server-NIFKZI4F.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -20,6 +20,8 @@ interface TemplateContext {
|
|
|
20
20
|
projectName: string;
|
|
21
21
|
packageManager: PackageManager;
|
|
22
22
|
koraVersion: string;
|
|
23
|
+
/** `@korajs/tauri` version for the desktop template (its own version line). */
|
|
24
|
+
tauriVersion?: string;
|
|
23
25
|
dbProvider?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -29,6 +31,15 @@ interface TemplateContext {
|
|
|
29
31
|
declare class CliError extends KoraError {
|
|
30
32
|
constructor(message: string, context?: Record<string, unknown>);
|
|
31
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Thrown when `kora deploy` targets a platform whose adapter is not implemented yet
|
|
36
|
+
* (DX-8). Raised before any artifact or deploy state is written.
|
|
37
|
+
*/
|
|
38
|
+
declare class DeployPlatformUnavailableError extends KoraError {
|
|
39
|
+
readonly platform: string;
|
|
40
|
+
readonly available: readonly string[];
|
|
41
|
+
constructor(platform: string, available: readonly string[]);
|
|
42
|
+
}
|
|
32
43
|
/**
|
|
33
44
|
* Thrown when the target project directory already exists.
|
|
34
45
|
*/
|
|
@@ -168,6 +179,15 @@ declare const deployCommand: citty.CommandDef<{
|
|
|
168
179
|
|
|
169
180
|
declare const DEPLOY_PLATFORMS: readonly ["fly", "railway", "aws-ecs", "aws-lightsail", "render", "docker", "kora-cloud"];
|
|
170
181
|
type DeployPlatform = (typeof DEPLOY_PLATFORMS)[number];
|
|
182
|
+
/**
|
|
183
|
+
* Platforms `kora deploy` can actually deploy to today (DX-8). The others are listed as
|
|
184
|
+
* "coming soon" and refused before anything is written.
|
|
185
|
+
*/
|
|
186
|
+
declare const IMPLEMENTED_DEPLOY_PLATFORMS: readonly ["fly", "railway", "aws-ecs", "aws-lightsail"];
|
|
187
|
+
/**
|
|
188
|
+
* Whether `kora deploy` has a working adapter for the platform.
|
|
189
|
+
*/
|
|
190
|
+
declare function isImplementedDeployPlatform(platform: DeployPlatform): platform is (typeof IMPLEMENTED_DEPLOY_PLATFORMS)[number];
|
|
171
191
|
interface ProjectConfig {
|
|
172
192
|
projectRoot: string;
|
|
173
193
|
appName: string;
|
|
@@ -749,4 +769,4 @@ interface SyncProviderPresetOptions {
|
|
|
749
769
|
*/
|
|
750
770
|
declare function applySyncProviderPreset(options: SyncProviderPresetOptions): Promise<void>;
|
|
751
771
|
|
|
752
|
-
export { AGENTS_FRAMEWORKS, type AgentsFramework, type AuthOption, type BuildArtifacts, ClackPromptClient, CliError, type ClientBuildOptions, type ClientBuildResult, type ContextAwareDeployAdapter, type CreateFlags, type CreatePreferences, DEPLOY_PLATFORMS, type DatabaseOption, type DatabaseProviderOption, type DeployAdapter, type DeployPlatform, type DeployResult, type DeployState, type DeployStateCreateInput, type DeployStatePatch, type DeploymentStatus, DevServerError, type DockerfileOptions, FlyAdapter, type FlyAdapterContext, type FlyAdapterOptions, type FlyCommandResult, type FlyCommandRunner, type FlyTomlOptions, type FrameworkOption, InvalidProjectError, type LogLine, type LogOptions, NodeFlyCommandRunner, NodeRailwayCommandRunner, PACKAGE_MANAGERS, type PackageManager, type PreferenceResolutionResult, PreferenceStore, type ProjectConfig, ProjectExistsError, type ProjectNameValidationResult, PromptCancelledError, type PromptClient, type ProvisionResult, RailwayAdapter, type RailwayAdapterContext, type RailwayAdapterOptions, type RailwayCommandResult, type RailwayCommandRunner, type RailwayJsonOptions, ReadlinePromptClient, SchemaNotFoundError, type SelectOption, type ServerBundleOptions, type ServerBundleResult, StubDeployAdapter, TEMPLATES, TEMPLATE_INFO, type TemplateContext, type TemplateInfo, type TemplateName, type TemplateSelectionInput, agentsCommand, applySyncProviderPreset, buildClient, bundleServer, createDeployAdapter, createPromptClient, deployCommand, detectFrameworkFromManifest, determineTemplateFromSelections, generateAgentsMd, generateDeployPackageJson, generateDockerIgnore, generateDockerfile, generateFlyToml, generateRailwayJson, generateTypes, getCreatePreferencesOrDefault, getDefaultCreatePreferences, isAgentsFramework, isAuthValue, isDatabaseProviderValue, isDatabaseValue, isDeployPlatform, isFrameworkValue, readDeployState, resetDeployState, resolveCreatePreferencesFlow, resolveDeployDirectory, resolveDeployStatePath, saveResolvedPreferences, shouldSavePreferences, updateDeployState, validateProjectName, writeDeployState, writeDockerIgnoreArtifact, writeDockerfileArtifact, writeFlyTomlArtifact, writeRailwayJsonArtifact };
|
|
772
|
+
export { AGENTS_FRAMEWORKS, type AgentsFramework, type AuthOption, type BuildArtifacts, ClackPromptClient, CliError, type ClientBuildOptions, type ClientBuildResult, type ContextAwareDeployAdapter, type CreateFlags, type CreatePreferences, DEPLOY_PLATFORMS, type DatabaseOption, type DatabaseProviderOption, type DeployAdapter, type DeployPlatform, DeployPlatformUnavailableError, type DeployResult, type DeployState, type DeployStateCreateInput, type DeployStatePatch, type DeploymentStatus, DevServerError, type DockerfileOptions, FlyAdapter, type FlyAdapterContext, type FlyAdapterOptions, type FlyCommandResult, type FlyCommandRunner, type FlyTomlOptions, type FrameworkOption, IMPLEMENTED_DEPLOY_PLATFORMS, InvalidProjectError, type LogLine, type LogOptions, NodeFlyCommandRunner, NodeRailwayCommandRunner, PACKAGE_MANAGERS, type PackageManager, type PreferenceResolutionResult, PreferenceStore, type ProjectConfig, ProjectExistsError, type ProjectNameValidationResult, PromptCancelledError, type PromptClient, type ProvisionResult, RailwayAdapter, type RailwayAdapterContext, type RailwayAdapterOptions, type RailwayCommandResult, type RailwayCommandRunner, type RailwayJsonOptions, ReadlinePromptClient, SchemaNotFoundError, type SelectOption, type ServerBundleOptions, type ServerBundleResult, StubDeployAdapter, TEMPLATES, TEMPLATE_INFO, type TemplateContext, type TemplateInfo, type TemplateName, type TemplateSelectionInput, agentsCommand, applySyncProviderPreset, buildClient, bundleServer, createDeployAdapter, createPromptClient, deployCommand, detectFrameworkFromManifest, determineTemplateFromSelections, generateAgentsMd, generateDeployPackageJson, generateDockerIgnore, generateDockerfile, generateFlyToml, generateRailwayJson, generateTypes, getCreatePreferencesOrDefault, getDefaultCreatePreferences, isAgentsFramework, isAuthValue, isDatabaseProviderValue, isDatabaseValue, isDeployPlatform, isFrameworkValue, isImplementedDeployPlatform, readDeployState, resetDeployState, resolveCreatePreferencesFlow, resolveDeployDirectory, resolveDeployStatePath, saveResolvedPreferences, shouldSavePreferences, updateDeployState, validateProjectName, writeDeployState, writeDockerIgnoreArtifact, writeDockerfileArtifact, writeFlyTomlArtifact, writeRailwayJsonArtifact };
|
package/dist/index.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ interface TemplateContext {
|
|
|
20
20
|
projectName: string;
|
|
21
21
|
packageManager: PackageManager;
|
|
22
22
|
koraVersion: string;
|
|
23
|
+
/** `@korajs/tauri` version for the desktop template (its own version line). */
|
|
24
|
+
tauriVersion?: string;
|
|
23
25
|
dbProvider?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -29,6 +31,15 @@ interface TemplateContext {
|
|
|
29
31
|
declare class CliError extends KoraError {
|
|
30
32
|
constructor(message: string, context?: Record<string, unknown>);
|
|
31
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Thrown when `kora deploy` targets a platform whose adapter is not implemented yet
|
|
36
|
+
* (DX-8). Raised before any artifact or deploy state is written.
|
|
37
|
+
*/
|
|
38
|
+
declare class DeployPlatformUnavailableError extends KoraError {
|
|
39
|
+
readonly platform: string;
|
|
40
|
+
readonly available: readonly string[];
|
|
41
|
+
constructor(platform: string, available: readonly string[]);
|
|
42
|
+
}
|
|
32
43
|
/**
|
|
33
44
|
* Thrown when the target project directory already exists.
|
|
34
45
|
*/
|
|
@@ -168,6 +179,15 @@ declare const deployCommand: citty.CommandDef<{
|
|
|
168
179
|
|
|
169
180
|
declare const DEPLOY_PLATFORMS: readonly ["fly", "railway", "aws-ecs", "aws-lightsail", "render", "docker", "kora-cloud"];
|
|
170
181
|
type DeployPlatform = (typeof DEPLOY_PLATFORMS)[number];
|
|
182
|
+
/**
|
|
183
|
+
* Platforms `kora deploy` can actually deploy to today (DX-8). The others are listed as
|
|
184
|
+
* "coming soon" and refused before anything is written.
|
|
185
|
+
*/
|
|
186
|
+
declare const IMPLEMENTED_DEPLOY_PLATFORMS: readonly ["fly", "railway", "aws-ecs", "aws-lightsail"];
|
|
187
|
+
/**
|
|
188
|
+
* Whether `kora deploy` has a working adapter for the platform.
|
|
189
|
+
*/
|
|
190
|
+
declare function isImplementedDeployPlatform(platform: DeployPlatform): platform is (typeof IMPLEMENTED_DEPLOY_PLATFORMS)[number];
|
|
171
191
|
interface ProjectConfig {
|
|
172
192
|
projectRoot: string;
|
|
173
193
|
appName: string;
|
|
@@ -749,4 +769,4 @@ interface SyncProviderPresetOptions {
|
|
|
749
769
|
*/
|
|
750
770
|
declare function applySyncProviderPreset(options: SyncProviderPresetOptions): Promise<void>;
|
|
751
771
|
|
|
752
|
-
export { AGENTS_FRAMEWORKS, type AgentsFramework, type AuthOption, type BuildArtifacts, ClackPromptClient, CliError, type ClientBuildOptions, type ClientBuildResult, type ContextAwareDeployAdapter, type CreateFlags, type CreatePreferences, DEPLOY_PLATFORMS, type DatabaseOption, type DatabaseProviderOption, type DeployAdapter, type DeployPlatform, type DeployResult, type DeployState, type DeployStateCreateInput, type DeployStatePatch, type DeploymentStatus, DevServerError, type DockerfileOptions, FlyAdapter, type FlyAdapterContext, type FlyAdapterOptions, type FlyCommandResult, type FlyCommandRunner, type FlyTomlOptions, type FrameworkOption, InvalidProjectError, type LogLine, type LogOptions, NodeFlyCommandRunner, NodeRailwayCommandRunner, PACKAGE_MANAGERS, type PackageManager, type PreferenceResolutionResult, PreferenceStore, type ProjectConfig, ProjectExistsError, type ProjectNameValidationResult, PromptCancelledError, type PromptClient, type ProvisionResult, RailwayAdapter, type RailwayAdapterContext, type RailwayAdapterOptions, type RailwayCommandResult, type RailwayCommandRunner, type RailwayJsonOptions, ReadlinePromptClient, SchemaNotFoundError, type SelectOption, type ServerBundleOptions, type ServerBundleResult, StubDeployAdapter, TEMPLATES, TEMPLATE_INFO, type TemplateContext, type TemplateInfo, type TemplateName, type TemplateSelectionInput, agentsCommand, applySyncProviderPreset, buildClient, bundleServer, createDeployAdapter, createPromptClient, deployCommand, detectFrameworkFromManifest, determineTemplateFromSelections, generateAgentsMd, generateDeployPackageJson, generateDockerIgnore, generateDockerfile, generateFlyToml, generateRailwayJson, generateTypes, getCreatePreferencesOrDefault, getDefaultCreatePreferences, isAgentsFramework, isAuthValue, isDatabaseProviderValue, isDatabaseValue, isDeployPlatform, isFrameworkValue, readDeployState, resetDeployState, resolveCreatePreferencesFlow, resolveDeployDirectory, resolveDeployStatePath, saveResolvedPreferences, shouldSavePreferences, updateDeployState, validateProjectName, writeDeployState, writeDockerIgnoreArtifact, writeDockerfileArtifact, writeFlyTomlArtifact, writeRailwayJsonArtifact };
|
|
772
|
+
export { AGENTS_FRAMEWORKS, type AgentsFramework, type AuthOption, type BuildArtifacts, ClackPromptClient, CliError, type ClientBuildOptions, type ClientBuildResult, type ContextAwareDeployAdapter, type CreateFlags, type CreatePreferences, DEPLOY_PLATFORMS, type DatabaseOption, type DatabaseProviderOption, type DeployAdapter, type DeployPlatform, DeployPlatformUnavailableError, type DeployResult, type DeployState, type DeployStateCreateInput, type DeployStatePatch, type DeploymentStatus, DevServerError, type DockerfileOptions, FlyAdapter, type FlyAdapterContext, type FlyAdapterOptions, type FlyCommandResult, type FlyCommandRunner, type FlyTomlOptions, type FrameworkOption, IMPLEMENTED_DEPLOY_PLATFORMS, InvalidProjectError, type LogLine, type LogOptions, NodeFlyCommandRunner, NodeRailwayCommandRunner, PACKAGE_MANAGERS, type PackageManager, type PreferenceResolutionResult, PreferenceStore, type ProjectConfig, ProjectExistsError, type ProjectNameValidationResult, PromptCancelledError, type PromptClient, type ProvisionResult, RailwayAdapter, type RailwayAdapterContext, type RailwayAdapterOptions, type RailwayCommandResult, type RailwayCommandRunner, type RailwayJsonOptions, ReadlinePromptClient, SchemaNotFoundError, type SelectOption, type ServerBundleOptions, type ServerBundleResult, StubDeployAdapter, TEMPLATES, TEMPLATE_INFO, type TemplateContext, type TemplateInfo, type TemplateName, type TemplateSelectionInput, agentsCommand, applySyncProviderPreset, buildClient, bundleServer, createDeployAdapter, createPromptClient, deployCommand, detectFrameworkFromManifest, determineTemplateFromSelections, generateAgentsMd, generateDeployPackageJson, generateDockerIgnore, generateDockerfile, generateFlyToml, generateRailwayJson, generateTypes, getCreatePreferencesOrDefault, getDefaultCreatePreferences, isAgentsFramework, isAuthValue, isDatabaseProviderValue, isDatabaseValue, isDeployPlatform, isFrameworkValue, isImplementedDeployPlatform, readDeployState, resetDeployState, resolveCreatePreferencesFlow, resolveDeployDirectory, resolveDeployStatePath, saveResolvedPreferences, shouldSavePreferences, updateDeployState, validateProjectName, writeDeployState, writeDockerIgnoreArtifact, writeDockerfileArtifact, writeFlyTomlArtifact, writeRailwayJsonArtifact };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
AGENTS_FRAMEWORKS,
|
|
4
4
|
DEPLOY_PLATFORMS,
|
|
5
5
|
FlyAdapter,
|
|
6
|
+
IMPLEMENTED_DEPLOY_PLATFORMS,
|
|
6
7
|
NodeFlyCommandRunner,
|
|
7
8
|
NodeRailwayCommandRunner,
|
|
8
9
|
RailwayAdapter,
|
|
@@ -22,6 +23,7 @@ import {
|
|
|
22
23
|
generateTypes,
|
|
23
24
|
isAgentsFramework,
|
|
24
25
|
isDeployPlatform,
|
|
26
|
+
isImplementedDeployPlatform,
|
|
25
27
|
readDeployState,
|
|
26
28
|
resetDeployState,
|
|
27
29
|
resolveDeployDirectory,
|
|
@@ -32,10 +34,11 @@ import {
|
|
|
32
34
|
writeDockerfileArtifact,
|
|
33
35
|
writeFlyTomlArtifact,
|
|
34
36
|
writeRailwayJsonArtifact
|
|
35
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-TVIRMDER.js";
|
|
36
38
|
import {
|
|
37
39
|
ClackPromptClient,
|
|
38
40
|
CliError,
|
|
41
|
+
DeployPlatformUnavailableError,
|
|
39
42
|
DevServerError,
|
|
40
43
|
InvalidProjectError,
|
|
41
44
|
PACKAGE_MANAGERS,
|
|
@@ -59,15 +62,17 @@ import {
|
|
|
59
62
|
saveResolvedPreferences,
|
|
60
63
|
shouldSavePreferences,
|
|
61
64
|
validateProjectName
|
|
62
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-7Y4J2GXP.js";
|
|
63
66
|
import "./chunk-5NI2FEQL.js";
|
|
64
67
|
export {
|
|
65
68
|
AGENTS_FRAMEWORKS,
|
|
66
69
|
ClackPromptClient,
|
|
67
70
|
CliError,
|
|
68
71
|
DEPLOY_PLATFORMS,
|
|
72
|
+
DeployPlatformUnavailableError,
|
|
69
73
|
DevServerError,
|
|
70
74
|
FlyAdapter,
|
|
75
|
+
IMPLEMENTED_DEPLOY_PLATFORMS,
|
|
71
76
|
InvalidProjectError,
|
|
72
77
|
NodeFlyCommandRunner,
|
|
73
78
|
NodeRailwayCommandRunner,
|
|
@@ -105,6 +110,7 @@ export {
|
|
|
105
110
|
isDatabaseValue,
|
|
106
111
|
isDeployPlatform,
|
|
107
112
|
isFrameworkValue,
|
|
113
|
+
isImplementedDeployPlatform,
|
|
108
114
|
readDeployState,
|
|
109
115
|
resetDeployState,
|
|
110
116
|
resolveCreatePreferencesFlow,
|
|
@@ -63,6 +63,15 @@ function parseJsonColumn(raw) {
|
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
function stripOpMetadata(data) {
|
|
67
|
+
if (data === null) return null;
|
|
68
|
+
const fields = {};
|
|
69
|
+
for (const [key, value] of Object.entries(data)) {
|
|
70
|
+
if (key.startsWith("__kora_") && key.endsWith("__")) continue;
|
|
71
|
+
fields[key] = value;
|
|
72
|
+
}
|
|
73
|
+
return Object.keys(fields).length > 0 ? fields : null;
|
|
74
|
+
}
|
|
66
75
|
function parseJsonArrayColumn(raw) {
|
|
67
76
|
if (typeof raw !== "string" || raw.length === 0) {
|
|
68
77
|
return [];
|
|
@@ -262,7 +271,7 @@ var StudioDbReader = class _StudioDbReader {
|
|
|
262
271
|
nodeId: String(row.node_id),
|
|
263
272
|
type: String(row.type),
|
|
264
273
|
recordId: String(row.record_id),
|
|
265
|
-
data: parseJsonColumn(row.data),
|
|
274
|
+
data: stripOpMetadata(parseJsonColumn(row.data)),
|
|
266
275
|
previousData: parseJsonColumn(row.previous_data),
|
|
267
276
|
timestamp: parseVersionStamp(row.timestamp),
|
|
268
277
|
sequenceNumber: Number(row.sequence_number),
|
|
@@ -1735,4 +1744,4 @@ function sendJson(res, status, body) {
|
|
|
1735
1744
|
export {
|
|
1736
1745
|
startStudioServer
|
|
1737
1746
|
};
|
|
1738
|
-
//# sourceMappingURL=studio-server-
|
|
1747
|
+
//# sourceMappingURL=studio-server-XLGZNGG5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/studio/studio-server.ts","../src/commands/studio/db-reader.ts","../src/commands/studio/studio-replay.ts","../src/commands/studio/studio-ui.ts","../src/commands/studio/studio-ui-app.ts","../src/commands/studio/studio-ui-css.ts"],"sourcesContent":["import type { IncomingMessage, Server, ServerResponse } from 'node:http'\nimport { createServer } from 'node:http'\nimport { StudioDbReader } from './db-reader'\nimport type { LabManager } from './lab-manager'\nimport { buildCausalDag, replayToOperation } from './studio-replay'\nimport { STUDIO_HTML } from './studio-ui'\nimport { STUDIO_APP_JS } from './studio-ui-app'\nimport { STUDIO_CSS } from './studio-ui-css'\n\n/**\n * Kora Studio HTTP server.\n *\n * Two modes share one inspection surface:\n * - FILE mode: read-only against a user database. Strictly GET.\n * - LAB mode: an in-process multi-device sync laboratory. Lab routes accept\n * POST, but only ever touch the Lab's throwaway databases — a user database\n * opened by Studio is never writable, in any mode.\n *\n * Live updates flow over Server-Sent Events: file mode polls SQLite's\n * data_version (cheap, cross-connection), lab mode pushes real device events.\n */\nexport interface StudioServerOptions {\n\tport: number\n\thost?: string\n\t/** File mode: path of the database to inspect. */\n\tdbPath?: string\n\t/** Lab mode: the running lab. */\n\tlab?: LabManager\n\t/** Spectator mode: live read-only replica of a production sync server. */\n\tspectator?: import('./spectator-manager').SpectatorManager\n}\n\nexport interface StudioServer {\n\tserver: Server\n\tport: number\n\turl: string\n\tclose(): Promise<void>\n}\n\ninterface StudioContext {\n\tmode: 'file' | 'lab' | 'spectator'\n\tlab: LabManager | null\n\tspectator: import('./spectator-manager').SpectatorManager | null\n\tmainReader: StudioDbReader | null\n\t/** Lazily-opened read-only readers for lab device DBs. */\n\tdeviceReaders: Map<string, StudioDbReader>\n}\n\nconst SSE_POLL_MS = 700\n\nexport async function startStudioServer(options: StudioServerOptions): Promise<StudioServer> {\n\tconst host = options.host ?? '127.0.0.1'\n\t// The spectator's replica is inspected exactly like a file-mode database.\n\tconst dbPath = options.spectator ? options.spectator.dbPath : options.dbPath\n\tconst context: StudioContext = {\n\t\tmode: options.spectator ? 'spectator' : options.lab ? 'lab' : 'file',\n\t\tlab: options.lab ?? null,\n\t\tspectator: options.spectator ?? null,\n\t\tmainReader: dbPath ? await StudioDbReader.open(dbPath) : null,\n\t\tdeviceReaders: new Map(),\n\t}\n\n\tconst sseClients = new Set<ServerResponse>()\n\tconst broadcast = (event: string, data: unknown): void => {\n\t\tconst payload = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`\n\t\tfor (const client of sseClients) {\n\t\t\tclient.write(payload)\n\t\t}\n\t}\n\n\t// Spectator mode: forward live client events (op applied, sync, merges).\n\tlet unsubscribeSpectator: (() => void) | null = null\n\tif (context.spectator) {\n\t\tunsubscribeSpectator = context.spectator.onEvent((event) => {\n\t\t\tbroadcast('spectator', event)\n\t\t\tbroadcast('change', { at: event.at })\n\t\t})\n\t}\n\n\t// File mode: poll the DB's data_version and broadcast change ticks.\n\tlet pollTimer: ReturnType<typeof setInterval> | null = null\n\tif (context.mode === 'file' && context.mainReader) {\n\t\tlet lastFingerprint = context.mainReader.fingerprint()\n\t\tpollTimer = setInterval(() => {\n\t\t\ttry {\n\t\t\t\tconst current = context.mainReader?.fingerprint()\n\t\t\t\tif (current !== undefined && current !== lastFingerprint) {\n\t\t\t\t\tlastFingerprint = current\n\t\t\t\t\tbroadcast('change', { at: Date.now() })\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// DB temporarily busy — next tick will retry.\n\t\t\t}\n\t\t}, SSE_POLL_MS)\n\t}\n\n\t// Lab mode: forward live device events.\n\tlet unsubscribeLab: (() => void) | null = null\n\tif (context.lab) {\n\t\tunsubscribeLab = context.lab.onEvent((event) => {\n\t\t\tbroadcast('lab', event)\n\t\t\tbroadcast('change', { at: event.at })\n\t\t})\n\t}\n\n\tconst server = createServer((req, res) => {\n\t\thandleRequest(context, sseClients, req, res).catch((error: unknown) => {\n\t\t\tconst message = error instanceof Error ? error.message : 'Internal error'\n\t\t\tif (!res.headersSent) {\n\t\t\t\tsendJson(res, 500, { error: message })\n\t\t\t} else {\n\t\t\t\tres.end()\n\t\t\t}\n\t\t})\n\t})\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tserver.once('error', reject)\n\t\tserver.listen(options.port, host, () => {\n\t\t\tserver.removeListener('error', reject)\n\t\t\tresolve()\n\t\t})\n\t})\n\n\tconst address = server.address()\n\tconst port = typeof address === 'object' && address !== null ? address.port : options.port\n\n\treturn {\n\t\tserver,\n\t\tport,\n\t\turl: `http://${host}:${port}`,\n\t\tclose: async () => {\n\t\t\tif (pollTimer) {\n\t\t\t\tclearInterval(pollTimer)\n\t\t\t}\n\t\t\tunsubscribeSpectator?.()\n\t\t\tunsubscribeLab?.()\n\t\t\tfor (const client of sseClients) {\n\t\t\t\tclient.end()\n\t\t\t}\n\t\t\tsseClients.clear()\n\t\t\tcontext.mainReader?.close()\n\t\t\tfor (const [, reader] of context.deviceReaders) {\n\t\t\t\treader.close()\n\t\t\t}\n\t\t\tcontext.deviceReaders.clear()\n\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\tserver.close((error) => (error ? reject(error) : resolve()))\n\t\t\t})\n\t\t},\n\t}\n}\n\nasync function resolveReader(context: StudioContext, url: URL): Promise<StudioDbReader> {\n\tconst device = url.searchParams.get('device')\n\tif (device && context.lab) {\n\t\tconst cached = context.deviceReaders.get(device)\n\t\tif (cached) {\n\t\t\treturn cached\n\t\t}\n\t\tconst state = context.lab.deviceState(device)\n\t\tconst reader = await StudioDbReader.open(state.dbPath)\n\t\tcontext.deviceReaders.set(device, reader)\n\t\treturn reader\n\t}\n\tif (context.mainReader) {\n\t\treturn context.mainReader\n\t}\n\t// Lab mode with no device specified: default to the first device.\n\tif (context.lab) {\n\t\tconst first = context.lab.listDevices()[0]\n\t\tif (first) {\n\t\t\treturn resolveReader(context, new URL(`${url.origin}${url.pathname}?device=${first.name}`))\n\t\t}\n\t}\n\tthrow new Error('No database available')\n}\n\nasync function handleRequest(\n\tcontext: StudioContext,\n\tsseClients: Set<ServerResponse>,\n\treq: IncomingMessage,\n\tres: ServerResponse,\n): Promise<void> {\n\tconst url = new URL(req.url ?? '/', 'http://studio.local')\n\tconst path = url.pathname\n\tconst method = req.method ?? 'GET'\n\n\t// ── Static assets ──\n\tif (method === 'GET' && (path === '/' || path === '/index.html')) {\n\t\tres.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })\n\t\tres.end(STUDIO_HTML)\n\t\treturn\n\t}\n\tif (method === 'GET' && path === '/app.js') {\n\t\tres.writeHead(200, { 'content-type': 'text/javascript; charset=utf-8' })\n\t\tres.end(STUDIO_APP_JS)\n\t\treturn\n\t}\n\tif (method === 'GET' && path === '/style.css') {\n\t\tres.writeHead(200, { 'content-type': 'text/css; charset=utf-8' })\n\t\tres.end(STUDIO_CSS)\n\t\treturn\n\t}\n\n\t// ── Live updates (SSE) ──\n\tif (method === 'GET' && path === '/api/events') {\n\t\tres.writeHead(200, {\n\t\t\t'content-type': 'text/event-stream',\n\t\t\t'cache-control': 'no-store',\n\t\t\tconnection: 'keep-alive',\n\t\t})\n\t\tres.write(`event: hello\\ndata: ${JSON.stringify({ mode: context.mode })}\\n\\n`)\n\t\tif (context.lab) {\n\t\t\tfor (const event of context.lab.recentEvents().slice(-100)) {\n\t\t\t\tres.write(`event: lab\\ndata: ${JSON.stringify(event)}\\n\\n`)\n\t\t\t}\n\t\t}\n\t\tif (context.spectator) {\n\t\t\tfor (const event of context.spectator.recentEvents().slice(-100)) {\n\t\t\t\tres.write(`event: spectator\\ndata: ${JSON.stringify(event)}\\n\\n`)\n\t\t\t}\n\t\t}\n\t\tsseClients.add(res)\n\t\treq.on('close', () => {\n\t\t\tsseClients.delete(res)\n\t\t})\n\t\treturn\n\t}\n\n\t// ── Lab routes (POST allowed; lab DBs are throwaway) ──\n\tif (path.startsWith('/api/lab')) {\n\t\tif (!context.lab) {\n\t\t\tsendJson(res, 404, {\n\t\t\t\terror: 'Studio is not running in lab mode. Start with: kora studio --lab',\n\t\t\t})\n\t\t\treturn\n\t\t}\n\t\tawait handleLabRoute(context, context.lab, req, res, url)\n\t\treturn\n\t}\n\n\t// ── Everything else is strictly read-only ──\n\tif (method !== 'GET') {\n\t\tsendJson(res, 405, { error: 'Studio data routes are read-only: GET only.' })\n\t\treturn\n\t}\n\n\tif (path === '/api/mode') {\n\t\tsendJson(res, 200, {\n\t\t\tmode: context.mode,\n\t\t\tdevices: context.lab?.listDevices().map((d) => d.name) ?? [],\n\t\t\t...(context.spectator ? { spectator: context.spectator.status() } : {}),\n\t\t})\n\t\treturn\n\t}\n\n\tif (path === '/api/spectator/status') {\n\t\tif (!context.spectator) {\n\t\t\tsendJson(res, 404, { error: 'Not in spectator mode' })\n\t\t\treturn\n\t\t}\n\t\tsendJson(res, 200, context.spectator.status())\n\t\treturn\n\t}\n\n\tif (path === '/api/overview') {\n\t\tconst reader = await resolveReader(context, url)\n\t\tsendJson(res, 200, reader.overview())\n\t\treturn\n\t}\n\n\tif (path === '/api/audit') {\n\t\tconst reader = await resolveReader(context, url)\n\t\tsendJson(res, 200, { traces: reader.auditTraces(intParam(url, 'limit') ?? 100) })\n\t\treturn\n\t}\n\n\tconst collectionMatch = path.match(\n\t\t/^\\/api\\/collections\\/([a-zA-Z0-9_]+)\\/(records|ops|replay|dag)(?:\\/([^/]+))?(?:\\/(ops))?$/,\n\t)\n\tif (collectionMatch) {\n\t\tconst [, collection, kind, recordId, sub] = collectionMatch\n\t\tif (!collection || !kind) {\n\t\t\tsendJson(res, 400, { error: 'Bad request' })\n\t\t\treturn\n\t\t}\n\t\tconst reader = await resolveReader(context, url)\n\t\tif (!reader.listCollections().includes(collection)) {\n\t\t\tsendJson(res, 404, { error: `Unknown collection \"${collection}\"` })\n\t\t\treturn\n\t\t}\n\n\t\tif (kind === 'replay') {\n\t\t\tconst ops = reader.allOperations(collection)\n\t\t\tconst upTo = url.searchParams.get('upTo')\n\t\t\tsendJson(res, 200, replayToOperation(ops, upTo))\n\t\t\treturn\n\t\t}\n\n\t\tif (kind === 'dag') {\n\t\t\tconst record = url.searchParams.get('record')\n\t\t\tconst limit = intParam(url, 'limit') ?? 200\n\t\t\tlet ops = reader.allOperations(collection)\n\t\t\tif (record) {\n\t\t\t\tops = ops.filter((o) => o.recordId === record)\n\t\t\t} else {\n\t\t\t\tops = ops.slice(-limit)\n\t\t\t}\n\t\t\tsendJson(res, 200, buildCausalDag(ops))\n\t\t\treturn\n\t\t}\n\n\t\tif (kind === 'ops') {\n\t\t\tsendJson(\n\t\t\t\tres,\n\t\t\t\t200,\n\t\t\t\treader.operations(collection, {\n\t\t\t\t\tlimit: intParam(url, 'limit'),\n\t\t\t\t\toffset: intParam(url, 'offset'),\n\t\t\t\t}),\n\t\t\t)\n\t\t\treturn\n\t\t}\n\n\t\tif (recordId && sub === 'ops') {\n\t\t\tsendJson(res, 200, { operations: reader.recordOperations(collection, recordId) })\n\t\t\treturn\n\t\t}\n\n\t\tif (recordId) {\n\t\t\tconst record = reader.record(collection, recordId)\n\t\t\tif (!record) {\n\t\t\t\tsendJson(res, 404, { error: `Record \"${recordId}\" not found` })\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst richtextPreviews = await decodeRichtextFields(\n\t\t\t\treader,\n\t\t\t\tcollection,\n\t\t\t\trecordId,\n\t\t\t\trecord.fields,\n\t\t\t)\n\t\t\tsendJson(res, 200, {\n\t\t\t\trecord,\n\t\t\t\trichtextPreviews,\n\t\t\t\toperations: reader.recordOperations(collection, recordId),\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\tsendJson(\n\t\t\tres,\n\t\t\t200,\n\t\t\treader.records(collection, {\n\t\t\t\tlimit: intParam(url, 'limit'),\n\t\t\t\toffset: intParam(url, 'offset'),\n\t\t\t\tincludeDeleted: url.searchParams.get('includeDeleted') === 'true',\n\t\t\t\tsearch: url.searchParams.get('search') ?? undefined,\n\t\t\t}),\n\t\t)\n\t\treturn\n\t}\n\n\tsendJson(res, 404, { error: `No route for ${path}` })\n}\n\n// ── Lab routes ───────────────────────────────────────────────────────────────\n\nasync function handleLabRoute(\n\tcontext: StudioContext,\n\tlab: LabManager,\n\treq: IncomingMessage,\n\tres: ServerResponse,\n\turl: URL,\n): Promise<void> {\n\tconst path = url.pathname\n\tconst method = req.method ?? 'GET'\n\n\tif (method === 'GET' && path === '/api/lab/state') {\n\t\tconst schema = lab.getSchema()\n\t\tsendJson(res, 200, {\n\t\t\tdevices: lab.listDevices(),\n\t\t\tserverOperations: lab.serverOperationCount(),\n\t\t\tcollections: Object.entries(schema.collections).map(([name, def]) => ({\n\t\t\t\tname,\n\t\t\t\tfields: Object.entries(def.fields).map(([fieldName, descriptor]) => ({\n\t\t\t\t\tname: fieldName,\n\t\t\t\t\tkind: descriptor.kind,\n\t\t\t\t\toptional: !descriptor.required,\n\t\t\t\t\tenumValues: descriptor.enumValues ?? null,\n\t\t\t\t\tdefaultValue: descriptor.defaultValue ?? null,\n\t\t\t\t})),\n\t\t\t})),\n\t\t})\n\t\treturn\n\t}\n\n\tif (method === 'GET' && path === '/api/lab/convergence') {\n\t\tsendJson(res, 200, await lab.convergence())\n\t\treturn\n\t}\n\n\tif (method !== 'POST') {\n\t\tsendJson(res, 405, { error: 'POST required' })\n\t\treturn\n\t}\n\n\tconst body = await readJsonBody(req)\n\n\tif (path === '/api/lab/devices') {\n\t\tconst state = await lab.addDevice(typeof body.name === 'string' ? body.name : undefined)\n\t\tsendJson(res, 200, state)\n\t\treturn\n\t}\n\n\tconst deviceMatch = path.match(\n\t\t/^\\/api\\/lab\\/devices\\/([a-zA-Z0-9_-]+)\\/(connect|disconnect|sync|chaos|insert|update|delete)$/,\n\t)\n\tif (!deviceMatch) {\n\t\tsendJson(res, 404, { error: `No lab route for ${path}` })\n\t\treturn\n\t}\n\tconst [, device, action] = deviceMatch\n\tif (!device || !action) {\n\t\tsendJson(res, 400, { error: 'Bad request' })\n\t\treturn\n\t}\n\n\tswitch (action) {\n\t\tcase 'connect':\n\t\t\tawait lab.connect(device)\n\t\t\tbreak\n\t\tcase 'disconnect': {\n\t\t\tawait lab.disconnect(device)\n\t\t\t// The device's DB file keeps changing while its reader is cached; drop\n\t\t\t// nothing — readers see committed WAL state per query. But a RESET of\n\t\t\t// the lab would invalidate paths; that's handled at close.\n\t\t\tbreak\n\t\t}\n\t\tcase 'sync':\n\t\t\tawait lab.sync(device)\n\t\t\tbreak\n\t\tcase 'chaos':\n\t\t\tsendJson(\n\t\t\t\tres,\n\t\t\t\t200,\n\t\t\t\tlab.setChaos(device, body as Partial<import('./lab-manager').LabChaosConfig>),\n\t\t\t)\n\t\t\treturn\n\t\tcase 'insert': {\n\t\t\tconst record = await lab.insert(\n\t\t\t\tdevice,\n\t\t\t\tString(body.collection ?? ''),\n\t\t\t\t(body.data as Record<string, unknown>) ?? {},\n\t\t\t)\n\t\t\tsendJson(res, 200, { record })\n\t\t\treturn\n\t\t}\n\t\tcase 'update': {\n\t\t\tconst record = await lab.update(\n\t\t\t\tdevice,\n\t\t\t\tString(body.collection ?? ''),\n\t\t\t\tString(body.id ?? ''),\n\t\t\t\t(body.data as Record<string, unknown>) ?? {},\n\t\t\t\t(body.increments as Record<string, number>) ?? undefined,\n\t\t\t)\n\t\t\tsendJson(res, 200, { record })\n\t\t\treturn\n\t\t}\n\t\tcase 'delete':\n\t\t\tawait lab.delete(device, String(body.collection ?? ''), String(body.id ?? ''))\n\t\t\tbreak\n\t}\n\tsendJson(res, 200, { ok: true, device: lab.deviceState(device) })\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function decodeRichtextFields(\n\treader: StudioDbReader,\n\tcollection: string,\n\trecordId: string,\n\tfields: Record<string, unknown>,\n): Promise<Record<string, string>> {\n\tconst previews: Record<string, string> = {}\n\tconst binaryFields = Object.entries(fields).filter(\n\t\t([, value]) => typeof value === 'string' && value.startsWith('<binary '),\n\t)\n\tif (binaryFields.length === 0) {\n\t\treturn previews\n\t}\n\tlet toPlainText: ((state: Uint8Array) => string) | null = null\n\ttry {\n\t\tconst storePkg = await import('@korajs/store')\n\t\ttoPlainText =\n\t\t\t(storePkg as { richtextToPlainText?: (s: Uint8Array) => string }).richtextToPlainText ?? null\n\t} catch {\n\t\treturn previews\n\t}\n\tif (!toPlainText) {\n\t\treturn previews\n\t}\n\tfor (const [field] of binaryFields) {\n\t\ttry {\n\t\t\tconst raw = reader.rawFieldValue(collection, recordId, field)\n\t\t\tif (raw instanceof Uint8Array || Buffer.isBuffer(raw)) {\n\t\t\t\tpreviews[field] = toPlainText(new Uint8Array(raw as Uint8Array))\n\t\t\t}\n\t\t} catch {\n\t\t\t// Not decodable as Yjs state — leave the byte summary.\n\t\t}\n\t}\n\treturn previews\n}\n\nasync function readJsonBody(req: IncomingMessage): Promise<Record<string, unknown>> {\n\tconst chunks: Buffer[] = []\n\tlet size = 0\n\tfor await (const chunk of req) {\n\t\tsize += (chunk as Buffer).length\n\t\tif (size > 256 * 1024) {\n\t\t\tthrow new Error('Request body too large')\n\t\t}\n\t\tchunks.push(chunk as Buffer)\n\t}\n\tif (chunks.length === 0) {\n\t\treturn {}\n\t}\n\ttry {\n\t\treturn JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>\n\t} catch {\n\t\tthrow new Error('Invalid JSON body')\n\t}\n}\n\nfunction intParam(url: URL, name: string): number | undefined {\n\tconst raw = url.searchParams.get(name)\n\tif (raw === null) {\n\t\treturn undefined\n\t}\n\tconst parsed = Number.parseInt(raw, 10)\n\treturn Number.isNaN(parsed) ? undefined : parsed\n}\n\nfunction sendJson(res: ServerResponse, status: number, body: unknown): void {\n\tres.writeHead(status, {\n\t\t'content-type': 'application/json; charset=utf-8',\n\t\t'cache-control': 'no-store',\n\t})\n\tres.end(JSON.stringify(body))\n}\n","import type BetterSqlite3 from 'better-sqlite3'\n\n/**\n * Read-only access layer for Kora Studio.\n *\n * Opens the SQLite database with `readonly: true` — Studio can NEVER write.\n * Any future editing feature must create real operations through the store\n * pipeline (never raw SQL), or it would corrupt the append-only op log and\n * violate content addressing.\n *\n * No schema file is required: collections are introspected as tables that\n * have a companion `_kora_ops_<name>` operation log table.\n */\n\n/** A parsed serialized HLC version stamp (\"wallTime:logical:nodeId\"). */\nexport interface ParsedVersion {\n\twallTime: number\n\tlogical: number\n\tnodeId: string\n}\n\nexport interface StudioCollectionInfo {\n\tname: string\n\tliveRecords: number\n\ttombstones: number\n\toperations: number\n\tcolumns: string[]\n}\n\nexport interface StudioOverview {\n\tdbPath: string\n\tcollections: StudioCollectionInfo[]\n\tversionVector: Array<{ nodeId: string; sequenceNumber: number }>\n\tmeta: Array<{ key: string; value: string }>\n\tpendingSyncOps: number\n\tauditTraces: number\n}\n\nexport interface StudioRecord {\n\tid: string\n\tfields: Record<string, unknown>\n\tcreatedAt: number\n\tupdatedAt: number\n\tdeleted: boolean\n\tversion: ParsedVersion | null\n\tfieldVersions: Record<string, ParsedVersion>\n}\n\nexport interface StudioOperation {\n\tid: string\n\tnodeId: string\n\ttype: string\n\trecordId: string\n\tdata: Record<string, unknown> | null\n\tpreviousData: Record<string, unknown> | null\n\ttimestamp: ParsedVersion | null\n\tsequenceNumber: number\n\tcausalDeps: string[]\n\tschemaVersion: number\n}\n\nexport interface StudioAuditTrace {\n\tid: string\n\trecordedAt: number\n\teventType: string\n\tcollection: string\n\trecordId: string\n\tfield: string\n\tstrategy: string\n\ttier: number\n\tconstraintName: string | null\n}\n\nconst KORA_TABLE_PREFIX = '_kora_'\nconst OPS_TABLE_PREFIX = '_kora_ops_'\n/** Metadata columns present on every materialized row. */\nconst META_COLUMNS = new Set([\n\t'id',\n\t'_created_at',\n\t'_updated_at',\n\t'_version',\n\t'_field_versions',\n\t'_deleted',\n])\n\n/** Parse a serialized HLC stamp; tolerates legacy/empty values with null. */\nexport function parseVersionStamp(raw: unknown): ParsedVersion | null {\n\tif (typeof raw !== 'string' || raw.length === 0) {\n\t\treturn null\n\t}\n\tconst parts = raw.split(':')\n\tif (parts.length < 3) {\n\t\treturn null\n\t}\n\tconst wallTime = Number.parseInt(parts[0] ?? '', 10)\n\tconst logical = Number.parseInt(parts[1] ?? '', 10)\n\tif (Number.isNaN(wallTime) || Number.isNaN(logical)) {\n\t\treturn null\n\t}\n\treturn { wallTime, logical, nodeId: parts.slice(2).join(':') }\n}\n\nfunction parseFieldVersionsColumn(raw: unknown): Record<string, ParsedVersion> {\n\tif (typeof raw !== 'string' || raw.length === 0) {\n\t\treturn {}\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as Record<string, unknown>\n\t\tconst result: Record<string, ParsedVersion> = {}\n\t\tfor (const [field, value] of Object.entries(parsed)) {\n\t\t\tconst version = parseVersionStamp(value)\n\t\t\tif (version) {\n\t\t\t\tresult[field] = version\n\t\t\t}\n\t\t}\n\t\treturn result\n\t} catch {\n\t\treturn {}\n\t}\n}\n\nfunction safeIdentifier(name: string): string {\n\tif (!/^[a-zA-Z0-9_]+$/.test(name)) {\n\t\tthrow new Error(`Unsafe SQL identifier: \"${name}\"`)\n\t}\n\treturn name\n}\n\nfunction parseJsonColumn(raw: unknown): Record<string, unknown> | null {\n\tif (typeof raw !== 'string' || raw.length === 0) {\n\t\treturn null\n\t}\n\ttry {\n\t\treturn JSON.parse(raw) as Record<string, unknown>\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/**\n * The op log embeds operation metadata in the data JSON under `__kora_*__` keys\n * (atomic ops, transaction id, mutation name, field versions, hash version). They are\n * not field values: Studio shows the data the operation wrote.\n */\nfunction stripOpMetadata(data: Record<string, unknown> | null): Record<string, unknown> | null {\n\tif (data === null) return null\n\tconst fields: Record<string, unknown> = {}\n\tfor (const [key, value] of Object.entries(data)) {\n\t\tif (key.startsWith('__kora_') && key.endsWith('__')) continue\n\t\tfields[key] = value\n\t}\n\treturn Object.keys(fields).length > 0 ? fields : null\n}\n\nfunction parseJsonArrayColumn(raw: unknown): string[] {\n\tif (typeof raw !== 'string' || raw.length === 0) {\n\t\treturn []\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as unknown\n\t\treturn Array.isArray(parsed) ? parsed.map(String) : []\n\t} catch {\n\t\treturn []\n\t}\n}\n\n/**\n * Read-only Studio access to a Kora SQLite database.\n */\nexport class StudioDbReader {\n\tprivate constructor(\n\t\tprivate readonly db: BetterSqlite3.Database,\n\t\tprivate readonly dbPath: string,\n\t) {}\n\n\t/**\n\t * Open a Kora database strictly read-only.\n\t * @throws when the file does not exist or is not a Kora database\n\t */\n\tstatic async open(dbPath: string): Promise<StudioDbReader> {\n\t\t// Dynamic import so better-sqlite3 loads only when Studio runs.\n\t\tlet Database: typeof BetterSqlite3\n\t\ttry {\n\t\t\tDatabase = (await import('better-sqlite3')).default\n\t\t} catch {\n\t\t\tthrow new Error(\n\t\t\t\t'Kora Studio needs the \"better-sqlite3\" package to open database files. Install it in your project: pnpm add -D better-sqlite3',\n\t\t\t)\n\t\t}\n\t\tconst db = new Database(dbPath, { readonly: true, fileMustExist: true })\n\t\tconst reader = new StudioDbReader(db, dbPath)\n\t\tif (reader.listCollections().length === 0 && !reader.hasKoraTables()) {\n\t\t\tdb.close()\n\t\t\tthrow new Error(`\"${dbPath}\" does not look like a Kora database (no _kora_* tables found).`)\n\t\t}\n\t\treturn reader\n\t}\n\n\tclose(): void {\n\t\tthis.db.close()\n\t}\n\n\tprivate hasKoraTables(): boolean {\n\t\tconst row = this.db\n\t\t\t.prepare(\"SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name LIKE ?\")\n\t\t\t.get(`${KORA_TABLE_PREFIX}%`) as { count: number }\n\t\treturn row.count > 0\n\t}\n\n\t/** Collections = tables that have a companion _kora_ops_<name> table. */\n\tlistCollections(): string[] {\n\t\tconst tables = this.db\n\t\t\t.prepare(\"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name\")\n\t\t\t.all() as Array<{ name: string }>\n\t\tconst names = new Set(tables.map((t) => t.name))\n\t\tconst collections: string[] = []\n\t\tfor (const { name } of tables) {\n\t\t\tif (name.startsWith(OPS_TABLE_PREFIX)) {\n\t\t\t\tconst collection = name.slice(OPS_TABLE_PREFIX.length)\n\t\t\t\tif (names.has(collection)) {\n\t\t\t\t\tcollections.push(collection)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn collections.sort()\n\t}\n\n\toverview(): StudioOverview {\n\t\tconst collections: StudioCollectionInfo[] = this.listCollections().map((name) => {\n\t\t\tconst table = safeIdentifier(name)\n\t\t\tconst live = this.db\n\t\t\t\t.prepare(`SELECT COUNT(*) as count FROM ${table} WHERE _deleted = 0`)\n\t\t\t\t.get() as { count: number }\n\t\t\tconst tombstones = this.db\n\t\t\t\t.prepare(`SELECT COUNT(*) as count FROM ${table} WHERE _deleted = 1`)\n\t\t\t\t.get() as { count: number }\n\t\t\tconst operations = this.db\n\t\t\t\t.prepare(`SELECT COUNT(*) as count FROM ${OPS_TABLE_PREFIX}${table}`)\n\t\t\t\t.get() as { count: number }\n\t\t\tconst columnRows = this.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{\n\t\t\t\tname: string\n\t\t\t}>\n\t\t\treturn {\n\t\t\t\tname,\n\t\t\t\tliveRecords: live.count,\n\t\t\t\ttombstones: tombstones.count,\n\t\t\t\toperations: operations.count,\n\t\t\t\tcolumns: columnRows.map((c) => c.name).filter((c) => !META_COLUMNS.has(c)),\n\t\t\t}\n\t\t})\n\n\t\tconst versionVector = this.tableExists('_kora_version_vector')\n\t\t\t? (this.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t'SELECT node_id as nodeId, sequence_number as sequenceNumber FROM _kora_version_vector ORDER BY node_id',\n\t\t\t\t\t)\n\t\t\t\t\t.all() as Array<{ nodeId: string; sequenceNumber: number }>)\n\t\t\t: []\n\n\t\tconst meta = this.tableExists('_kora_meta')\n\t\t\t? (this.db.prepare('SELECT key, value FROM _kora_meta ORDER BY key').all() as Array<{\n\t\t\t\t\tkey: string\n\t\t\t\t\tvalue: string\n\t\t\t\t}>)\n\t\t\t: []\n\n\t\tconst pendingSyncOps = this.tableExists('_kora_sync_queue')\n\t\t\t? (\n\t\t\t\t\tthis.db.prepare('SELECT COUNT(*) as count FROM _kora_sync_queue').get() as {\n\t\t\t\t\t\tcount: number\n\t\t\t\t\t}\n\t\t\t\t).count\n\t\t\t: 0\n\n\t\tconst auditTraces = this.tableExists('_kora_audit_traces')\n\t\t\t? (\n\t\t\t\t\tthis.db.prepare('SELECT COUNT(*) as count FROM _kora_audit_traces').get() as {\n\t\t\t\t\t\tcount: number\n\t\t\t\t\t}\n\t\t\t\t).count\n\t\t\t: 0\n\n\t\treturn { dbPath: this.dbPath, collections, versionVector, meta, pendingSyncOps, auditTraces }\n\t}\n\n\trecords(\n\t\tcollection: string,\n\t\toptions: {\n\t\t\tlimit?: number\n\t\t\toffset?: number\n\t\t\tincludeDeleted?: boolean\n\t\t\tsearch?: string\n\t\t} = {},\n\t): { records: StudioRecord[]; total: number } {\n\t\tconst table = safeIdentifier(collection)\n\t\tconst limit = Math.min(Math.max(options.limit ?? 50, 1), 500)\n\t\tconst offset = Math.max(options.offset ?? 0, 0)\n\n\t\tconst clauses: string[] = []\n\t\tconst params: unknown[] = []\n\t\tif (!options.includeDeleted) {\n\t\t\tclauses.push('_deleted = 0')\n\t\t}\n\t\tif (options.search && options.search.trim().length > 0) {\n\t\t\t// Search across id and every TEXT-affinity column.\n\t\t\tconst textColumns = (\n\t\t\t\tthis.db.prepare(`PRAGMA table_info(${table})`).all() as Array<{\n\t\t\t\t\tname: string\n\t\t\t\t\ttype: string\n\t\t\t\t}>\n\t\t\t).filter((c) => c.type.toUpperCase().includes('TEXT') || c.name === 'id')\n\t\t\tconst term = `%${options.search.trim()}%`\n\t\t\tconst likes = textColumns.map((c) => {\n\t\t\t\tparams.push(term)\n\t\t\t\treturn `${safeIdentifier(c.name)} LIKE ?`\n\t\t\t})\n\t\t\tif (likes.length > 0) {\n\t\t\t\tclauses.push(`(${likes.join(' OR ')})`)\n\t\t\t}\n\t\t}\n\t\tconst where = clauses.length > 0 ? `WHERE ${clauses.join(' AND ')}` : ''\n\n\t\tconst total = (\n\t\t\tthis.db.prepare(`SELECT COUNT(*) as count FROM ${table} ${where}`).get(...params) as {\n\t\t\t\tcount: number\n\t\t\t}\n\t\t).count\n\t\tconst rows = this.db\n\t\t\t.prepare(`SELECT * FROM ${table} ${where} ORDER BY _updated_at DESC LIMIT ? OFFSET ?`)\n\t\t\t.all(...params, limit, offset) as Array<Record<string, unknown>>\n\n\t\treturn { records: rows.map((row) => this.toStudioRecord(row)), total }\n\t}\n\n\t/** Full operation log for one collection (capped), for replay and the DAG. */\n\tallOperations(collection: string, cap = 5000): StudioOperation[] {\n\t\tconst table = safeIdentifier(collection)\n\t\tconst rows = this.db\n\t\t\t.prepare(`SELECT * FROM ${OPS_TABLE_PREFIX}${table} ORDER BY timestamp ASC LIMIT ?`)\n\t\t\t.all(Math.min(Math.max(cap, 1), 20000)) as Array<Record<string, unknown>>\n\t\treturn rows.map((row) => this.toStudioOperation(row))\n\t}\n\n\t/**\n\t * Cheap change fingerprint for live updates. SQLite's `data_version` PRAGMA\n\t * increments whenever ANOTHER connection commits — exactly what a read-only\n\t * watcher needs to know \"something changed, refetch\".\n\t */\n\tfingerprint(): string {\n\t\tconst row = this.db.prepare('PRAGMA data_version').get() as { data_version: number }\n\t\treturn String(row.data_version)\n\t}\n\n\t/** Raw value of a single column on a record (for richtext preview decode). */\n\trawFieldValue(collection: string, recordId: string, field: string): unknown {\n\t\tconst table = safeIdentifier(collection)\n\t\tconst column = safeIdentifier(field)\n\t\tconst row = this.db\n\t\t\t.prepare(`SELECT ${column} as value FROM ${table} WHERE id = ?`)\n\t\t\t.get(recordId) as { value: unknown } | undefined\n\t\treturn row?.value\n\t}\n\n\trecord(collection: string, recordId: string): StudioRecord | null {\n\t\tconst table = safeIdentifier(collection)\n\t\tconst row = this.db.prepare(`SELECT * FROM ${table} WHERE id = ?`).get(recordId) as\n\t\t\t| Record<string, unknown>\n\t\t\t| undefined\n\t\treturn row ? this.toStudioRecord(row) : null\n\t}\n\n\t/** Full operation history for one record, newest first. */\n\trecordOperations(collection: string, recordId: string): StudioOperation[] {\n\t\tconst table = safeIdentifier(collection)\n\t\tconst rows = this.db\n\t\t\t.prepare(\n\t\t\t\t`SELECT * FROM ${OPS_TABLE_PREFIX}${table} WHERE record_id = ? ORDER BY timestamp DESC`,\n\t\t\t)\n\t\t\t.all(recordId) as Array<Record<string, unknown>>\n\t\treturn rows.map((row) => this.toStudioOperation(row))\n\t}\n\n\toperations(\n\t\tcollection: string,\n\t\toptions: { limit?: number; offset?: number } = {},\n\t): { operations: StudioOperation[]; total: number } {\n\t\tconst table = safeIdentifier(collection)\n\t\tconst limit = Math.min(Math.max(options.limit ?? 50, 1), 500)\n\t\tconst offset = Math.max(options.offset ?? 0, 0)\n\t\tconst total = (\n\t\t\tthis.db.prepare(`SELECT COUNT(*) as count FROM ${OPS_TABLE_PREFIX}${table}`).get() as {\n\t\t\t\tcount: number\n\t\t\t}\n\t\t).count\n\t\tconst rows = this.db\n\t\t\t.prepare(`SELECT * FROM ${OPS_TABLE_PREFIX}${table} ORDER BY timestamp DESC LIMIT ? OFFSET ?`)\n\t\t\t.all(limit, offset) as Array<Record<string, unknown>>\n\t\treturn { operations: rows.map((row) => this.toStudioOperation(row)), total }\n\t}\n\n\tauditTraces(limit = 100): StudioAuditTrace[] {\n\t\tif (!this.tableExists('_kora_audit_traces')) {\n\t\t\treturn []\n\t\t}\n\t\tconst rows = this.db\n\t\t\t.prepare(\n\t\t\t\t`SELECT id, recorded_at, event_type, collection, record_id, field, strategy, tier, constraint_name\n\t\t\t\t FROM _kora_audit_traces ORDER BY recorded_at DESC LIMIT ?`,\n\t\t\t)\n\t\t\t.all(Math.min(Math.max(limit, 1), 500)) as Array<Record<string, unknown>>\n\t\treturn rows.map((row) => ({\n\t\t\tid: String(row.id),\n\t\t\trecordedAt: Number(row.recorded_at),\n\t\t\teventType: String(row.event_type),\n\t\t\tcollection: String(row.collection),\n\t\t\trecordId: String(row.record_id),\n\t\t\tfield: String(row.field),\n\t\t\tstrategy: String(row.strategy),\n\t\t\ttier: Number(row.tier),\n\t\t\tconstraintName: row.constraint_name === null ? null : String(row.constraint_name),\n\t\t}))\n\t}\n\n\tprivate tableExists(name: string): boolean {\n\t\tconst row = this.db\n\t\t\t.prepare(\"SELECT COUNT(*) as count FROM sqlite_master WHERE type='table' AND name = ?\")\n\t\t\t.get(name) as { count: number }\n\t\treturn row.count > 0\n\t}\n\n\tprivate toStudioRecord(row: Record<string, unknown>): StudioRecord {\n\t\tconst fields: Record<string, unknown> = {}\n\t\tfor (const [key, value] of Object.entries(row)) {\n\t\t\tif (!META_COLUMNS.has(key)) {\n\t\t\t\t// BLOBs (richtext Yjs state) are summarized, not dumped raw.\n\t\t\t\tfields[key] =\n\t\t\t\t\tvalue instanceof Uint8Array || Buffer.isBuffer(value)\n\t\t\t\t\t\t? `<binary ${(value as Uint8Array).byteLength} bytes>`\n\t\t\t\t\t\t: value\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tid: String(row.id),\n\t\t\tfields,\n\t\t\tcreatedAt: Number(row._created_at),\n\t\t\tupdatedAt: Number(row._updated_at),\n\t\t\tdeleted: row._deleted === 1,\n\t\t\tversion: parseVersionStamp(row._version),\n\t\t\tfieldVersions: parseFieldVersionsColumn(row._field_versions),\n\t\t}\n\t}\n\n\tprivate toStudioOperation(row: Record<string, unknown>): StudioOperation {\n\t\treturn {\n\t\t\tid: String(row.id),\n\t\t\tnodeId: String(row.node_id),\n\t\t\ttype: String(row.type),\n\t\t\trecordId: String(row.record_id),\n\t\t\tdata: stripOpMetadata(parseJsonColumn(row.data)),\n\t\t\tpreviousData: parseJsonColumn(row.previous_data),\n\t\t\ttimestamp: parseVersionStamp(row.timestamp),\n\t\t\tsequenceNumber: Number(row.sequence_number),\n\t\t\tcausalDeps: parseJsonArrayColumn(row.causal_deps),\n\t\t\tschemaVersion: Number(row.schema_version),\n\t\t}\n\t}\n}\n","import type { ParsedVersion, StudioOperation } from './db-reader'\n\n/**\n * Time travel and causal-graph helpers for Kora Studio.\n *\n * `replayToOperation` folds the operation log up to (and including) a chosen\n * operation, in HLC order, producing the record set as it existed at that\n * causal cut. Because per-field last-write-wins is deterministic and\n * order-independent, folding in HLC order reproduces exactly the state every\n * in-order device computed at that point — the same principle the store's own\n * materializer uses, applied read-only for inspection.\n */\n\nexport interface ReplayRecord {\n\tid: string\n\tfields: Record<string, unknown>\n\tdeleted: boolean\n\tlastWriterByField: Record<string, string>\n}\n\nexport interface ReplayResult {\n\t/** Records alive (or tombstoned) at the causal cut, keyed by record id. */\n\trecords: ReplayRecord[]\n\t/** How many operations were folded (position of the cut, 1-based). */\n\tappliedCount: number\n\ttotalCount: number\n\t/** The operation at the cut. */\n\tcutOperation: StudioOperation | null\n}\n\nfunction compareVersion(a: ParsedVersion | null, b: ParsedVersion | null): number {\n\tif (!a || !b) {\n\t\treturn a === b ? 0 : a ? 1 : -1\n\t}\n\tif (a.wallTime !== b.wallTime) {\n\t\treturn a.wallTime - b.wallTime\n\t}\n\tif (a.logical !== b.logical) {\n\t\treturn a.logical - b.logical\n\t}\n\treturn a.nodeId < b.nodeId ? -1 : a.nodeId > b.nodeId ? 1 : 0\n}\n\n/** Sort operations into the HLC total order (the canonical replay order). */\nexport function sortByHlc(ops: StudioOperation[]): StudioOperation[] {\n\treturn [...ops].sort((a, b) => compareVersion(a.timestamp, b.timestamp))\n}\n\n/**\n * Fold operations up to and including `upToOpId` (or all when null).\n */\nexport function replayToOperation(ops: StudioOperation[], upToOpId: string | null): ReplayResult {\n\tconst ordered = sortByHlc(ops)\n\tlet cutIndex = ordered.length - 1\n\tif (upToOpId !== null) {\n\t\tconst found = ordered.findIndex((o) => o.id === upToOpId)\n\t\tif (found === -1) {\n\t\t\tthrow new Error(`Operation \"${upToOpId}\" not found in the log`)\n\t\t}\n\t\tcutIndex = found\n\t}\n\n\tconst records = new Map<\n\t\tstring,\n\t\t{ fields: Record<string, unknown>; deleted: boolean; writers: Record<string, string> }\n\t>()\n\n\tfor (let i = 0; i <= cutIndex; i++) {\n\t\tconst operation = ordered[i]\n\t\tif (!operation) {\n\t\t\tcontinue\n\t\t}\n\t\tconst writer = operation.timestamp ? shortNode(operation.timestamp.nodeId) : '?'\n\t\tif (operation.type === 'insert' && operation.data) {\n\t\t\tconst writers: Record<string, string> = {}\n\t\t\tfor (const field of Object.keys(operation.data)) {\n\t\t\t\twriters[field] = writer\n\t\t\t}\n\t\t\trecords.set(operation.recordId, {\n\t\t\t\tfields: { ...operation.data },\n\t\t\t\tdeleted: false,\n\t\t\t\twriters,\n\t\t\t})\n\t\t} else if (operation.type === 'update' && operation.data) {\n\t\t\tconst existing = records.get(operation.recordId)\n\t\t\tif (existing) {\n\t\t\t\tfor (const [field, value] of Object.entries(operation.data)) {\n\t\t\t\t\texisting.fields[field] = value\n\t\t\t\t\texisting.writers[field] = writer\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Orphaned update (insert later in HLC order than the update is\n\t\t\t\t// impossible in HLC order — but a compacted log can lose the insert).\n\t\t\t\tconst writers: Record<string, string> = {}\n\t\t\t\tfor (const field of Object.keys(operation.data)) {\n\t\t\t\t\twriters[field] = writer\n\t\t\t\t}\n\t\t\t\trecords.set(operation.recordId, {\n\t\t\t\t\tfields: { ...operation.data },\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\twriters,\n\t\t\t\t})\n\t\t\t}\n\t\t} else if (operation.type === 'delete') {\n\t\t\tconst existing = records.get(operation.recordId)\n\t\t\tif (existing) {\n\t\t\t\texisting.deleted = true\n\t\t\t} else {\n\t\t\t\trecords.set(operation.recordId, { fields: {}, deleted: true, writers: {} })\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\trecords: [...records.entries()].map(([id, r]) => ({\n\t\t\tid,\n\t\t\tfields: r.fields,\n\t\t\tdeleted: r.deleted,\n\t\t\tlastWriterByField: r.writers,\n\t\t})),\n\t\tappliedCount: cutIndex + 1,\n\t\ttotalCount: ordered.length,\n\t\tcutOperation: ordered[cutIndex] ?? null,\n\t}\n}\n\n// ── Causal DAG ───────────────────────────────────────────────────────────────\n\nexport interface DagNode {\n\tid: string\n\ttype: string\n\trecordId: string\n\tnodeId: string\n\tshortNodeId: string\n\tsequenceNumber: number\n\twallTime: number\n\t/** Column in the layered layout (HLC order). */\n\tx: number\n\t/** Lane index (one lane per originating device). */\n\tlane: number\n\tdataPreview: string\n}\n\nexport interface DagEdge {\n\tfrom: string\n\tto: string\n}\n\nexport interface DagResult {\n\tnodes: DagNode[]\n\tedges: DagEdge[]\n\tlanes: Array<{ nodeId: string; shortNodeId: string }>\n}\n\n/**\n * Build a layered causal graph from operations: x = HLC order, one horizontal\n * lane per originating device, edges = causal dependencies. Concurrency is\n * VISIBLE as operations sharing an x-neighborhood on different lanes with no\n * path between them.\n */\nexport function buildCausalDag(ops: StudioOperation[]): DagResult {\n\tconst ordered = sortByHlc(ops)\n\tconst laneByNode = new Map<string, number>()\n\tconst lanes: Array<{ nodeId: string; shortNodeId: string }> = []\n\n\tfor (const operation of ordered) {\n\t\tif (!laneByNode.has(operation.nodeId)) {\n\t\t\tlaneByNode.set(operation.nodeId, lanes.length)\n\t\t\tlanes.push({ nodeId: operation.nodeId, shortNodeId: shortNode(operation.nodeId) })\n\t\t}\n\t}\n\n\tconst idSet = new Set(ordered.map((o) => o.id))\n\tconst nodes: DagNode[] = ordered.map((operation, index) => ({\n\t\tid: operation.id,\n\t\ttype: operation.type,\n\t\trecordId: operation.recordId,\n\t\tnodeId: operation.nodeId,\n\t\tshortNodeId: shortNode(operation.nodeId),\n\t\tsequenceNumber: operation.sequenceNumber,\n\t\twallTime: operation.timestamp?.wallTime ?? 0,\n\t\tx: index,\n\t\tlane: laneByNode.get(operation.nodeId) ?? 0,\n\t\tdataPreview: operation.data ? JSON.stringify(operation.data).slice(0, 80) : '',\n\t}))\n\n\tconst edges: DagEdge[] = []\n\tfor (const operation of ordered) {\n\t\tfor (const dep of operation.causalDeps) {\n\t\t\tif (idSet.has(dep)) {\n\t\t\t\tedges.push({ from: dep, to: operation.id })\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { nodes, edges, lanes }\n}\n\nfunction shortNode(nodeId: string): string {\n\treturn nodeId.length > 8 ? nodeId.slice(-8) : nodeId\n}\n","/**\n * Kora Studio HTML shell (served at /). The stylesheet and application are\n * served as separate routes so each stays an editable module; everything is\n * still fully self-contained — no CDN, no build step, works offline.\n */\nexport const STUDIO_HTML = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Kora Studio</title>\n<link rel=\"stylesheet\" href=\"/style.css\" />\n</head>\n<body>\n<header>\n <div class=\"logo\">kora <span>studio</span></div>\n <div class=\"modebadge\" id=\"modebadge\"></div>\n <div class=\"dbpath mono\" id=\"dbpath\"></div>\n <div class=\"live\" id=\"live\"><div class=\"dot\"></div><span>live</span></div>\n <button id=\"refresh\">Refresh</button>\n</header>\n<div class=\"tabs\" id=\"tabs\"></div>\n<div class=\"layout\">\n <nav class=\"sidebar\" id=\"sidebar\"></nav>\n <main id=\"main\"><div class=\"empty\">Loading…</div></main>\n</div>\n<div class=\"drawer\" id=\"drawer\"></div>\n<div class=\"dag-tip\" id=\"dagtip\"></div>\n<script src=\"/app.js\"></script>\n</body>\n</html>\n`\n","/** Kora Studio application (served at /app.js). Vanilla DOM, zero dependencies. */\nexport const STUDIO_APP_JS = `\n(function () {\n 'use strict';\n\n // ── State ──────────────────────────────────────────────────────────────────\n var state = {\n mode: 'file', // 'file' | 'lab'\n tab: 'data', // data | ops | timetravel | merges | sync | lab\n device: null, // selected lab device for data-ish tabs\n devices: [],\n collection: null,\n page: 0,\n includeDeleted: false,\n search: '',\n overview: null,\n ttPosition: null, // time-travel slider position\n ttPlaying: null,\n labState: null,\n feed: [],\n deviceColors: {},\n live: false\n };\n var PAGE = 50;\n var refreshTimer = null;\n\n // ── Utilities ──────────────────────────────────────────────────────────────\n function qs(sel) { return document.querySelector(sel); }\n function api(path) {\n var sep = path.indexOf('?') === -1 ? '?' : '&';\n var url = state.device ? path + sep + 'device=' + encodeURIComponent(state.device) : path;\n return fetch(url).then(function (r) {\n if (!r.ok) { return r.json().then(function (b) { throw new Error(b.error || r.statusText); }); }\n return r.json();\n });\n }\n function post(path, body) {\n return fetch(path, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body || {})\n }).then(function (r) {\n if (!r.ok) { return r.json().then(function (b) { throw new Error(b.error || r.statusText); }); }\n return r.json();\n });\n }\n function el(tag, attrs, children) {\n var node = document.createElement(tag);\n if (attrs) { Object.keys(attrs).forEach(function (k) {\n if (k === 'class') node.className = attrs[k];\n else if (k === 'text') node.textContent = attrs[k];\n else if (k === 'html') node.innerHTML = attrs[k];\n else if (k.slice(0, 2) === 'on') node.addEventListener(k.slice(2), attrs[k]);\n else node.setAttribute(k, attrs[k]);\n }); }\n (children || []).forEach(function (c) { if (c) node.appendChild(c); });\n return node;\n }\n function fmtTime(ms) {\n if (!ms) return '·';\n var d = new Date(ms);\n return d.toLocaleTimeString() + '.' + ('00' + d.getMilliseconds()).slice(-3);\n }\n function fmtDateTime(ms) {\n if (!ms) return '·';\n var d = new Date(ms);\n return d.toLocaleDateString() + ' ' + d.toLocaleTimeString();\n }\n function shortNode(id) { return id && id.length > 8 ? id.slice(-8) : (id || '·'); }\n function shortId(id) { return id && id.length > 13 ? id.slice(0, 13) + '…' : (id || ''); }\n function fmtVal(v) {\n if (v === null || v === undefined) return 'null';\n if (typeof v === 'object') return JSON.stringify(v);\n return String(v);\n }\n function deviceColor(name) {\n if (!(name in state.deviceColors)) {\n state.deviceColors[name] = 'dev-c' + (Object.keys(state.deviceColors).length % 6);\n }\n return state.deviceColors[name];\n }\n function debounceRefresh() {\n if (refreshTimer) clearTimeout(refreshTimer);\n refreshTimer = setTimeout(function () { render(false); }, 250);\n }\n\n // ── Boot + live events ─────────────────────────────────────────────────────\n fetch('/api/mode').then(function (r) { return r.json(); }).then(function (info) {\n state.mode = info.mode;\n state.devices = info.devices || [];\n if (state.mode === 'lab') { state.tab = 'lab'; state.device = state.devices[0] || null; }\n qs('#modebadge').textContent = state.mode;\n if (info.spectator) {\n qs('#dbpath').textContent = '⇄ ' + info.spectator.url +\n (info.spectator.connected ? ' · connected' : ' · connecting…') +\n ' · ' + info.spectator.operationsReceived + ' ops received';\n }\n connectEvents();\n render(true);\n });\n\n function connectEvents() {\n var source = new EventSource('/api/events');\n source.addEventListener('hello', function () {\n state.live = true; qs('#live').className = 'live on';\n });\n source.addEventListener('change', function () {\n if (state.tab !== 'timetravel') debounceRefresh();\n });\n source.addEventListener('lab', function (event) {\n var data = JSON.parse(event.data);\n state.feed.push(data);\n if (state.feed.length > 400) state.feed.shift();\n appendFeedLine(data);\n });\n source.addEventListener('spectator', function (event) {\n var data = JSON.parse(event.data);\n data.device = 'server';\n state.feed.push(data);\n if (state.feed.length > 400) state.feed.shift();\n appendFeedLine(data);\n // Keep the header status fresh as ops stream in.\n fetch('/api/spectator/status').then(function (r) { return r.json(); }).then(function (s) {\n qs('#dbpath').textContent = '⇄ ' + s.url +\n (s.connected ? ' · connected' : ' · reconnecting…') +\n ' · ' + s.operationsReceived + ' ops received';\n }).catch(function () {});\n });\n source.onerror = function () {\n state.live = false; qs('#live').className = 'live';\n };\n }\n\n // ── Tabs + chrome ──────────────────────────────────────────────────────────\n function renderTabs() {\n var tabs = qs('#tabs');\n tabs.textContent = '';\n var defs = [];\n if (state.mode === 'lab') defs.push(['lab', 'Lab']);\n defs.push(['data', 'Data'], ['ops', 'Operations'], ['timetravel', 'Time Travel'], ['merges', 'Merges'], ['sync', 'Sync']);\n defs.forEach(function (d) {\n tabs.appendChild(el('div', {\n class: 'tab' + (state.tab === d[0] ? ' active' : ''),\n text: d[1],\n onclick: function () { state.tab = d[0]; state.page = 0; closeDrawer(); render(true); }\n }));\n });\n tabs.appendChild(el('div', { class: 'spacer' }));\n if (state.mode === 'lab' && state.tab !== 'lab') {\n var pick = el('select', { onchange: function () { state.device = pick.value; state.page = 0; render(true); } },\n state.devices.map(function (d) {\n var o = el('option', { value: d, text: d });\n if (d === state.device) o.selected = true;\n return o;\n }));\n tabs.appendChild(el('div', { class: 'devicepick' }, [el('span', { text: 'device:' }), pick]));\n }\n }\n\n function renderSidebar() {\n var nav = qs('#sidebar');\n nav.textContent = '';\n if (state.tab === 'lab' || state.tab === 'sync' || state.tab === 'merges') {\n nav.style.display = 'none';\n return;\n }\n nav.style.display = '';\n nav.appendChild(el('h3', { text: 'Collections' }));\n (state.overview ? state.overview.collections : []).forEach(function (c) {\n nav.appendChild(el('div', {\n class: 'item' + (state.collection === c.name ? ' active' : ''),\n onclick: function () { state.collection = c.name; state.page = 0; closeDrawer(); render(false); }\n }, [\n el('span', { text: c.name }),\n el('span', { class: 'count', text: String(c.liveRecords) + (c.tombstones ? ' +' + c.tombstones + '†' : '') })\n ]));\n });\n }\n\n // ── Main render ────────────────────────────────────────────────────────────\n function render(full) {\n renderTabs();\n if (state.tab === 'lab') { renderSidebar(); renderLab(full); return; }\n api('/api/overview').then(function (overview) {\n state.overview = overview;\n qs('#dbpath').textContent = overview.dbPath || '';\n if (!state.collection || !overview.collections.some(function (c) { return c.name === state.collection; })) {\n state.collection = overview.collections.length ? overview.collections[0].name : null;\n }\n renderSidebar();\n var main = qs('#main');\n main.textContent = '';\n if (state.tab === 'data') return renderData(main);\n if (state.tab === 'ops') return renderOps(main);\n if (state.tab === 'timetravel') return renderTimeTravel(main);\n if (state.tab === 'merges') return renderMerges(main);\n if (state.tab === 'sync') return renderSync(main);\n }).catch(function (e) {\n qs('#main').innerHTML = '<div class=\"error\">' + e.message + '</div>';\n });\n }\n\n function toolbar(main, title, extra) {\n main.appendChild(el('div', { class: 'toolbar' }, [el('h2', { text: title })].concat(extra || [])));\n }\n function pager(main, total) {\n var pages = Math.max(1, Math.ceil(total / PAGE));\n main.appendChild(el('div', { class: 'pager' }, [\n el('button', { text: '‹ Prev', onclick: function () { if (state.page > 0) { state.page--; render(false); } } }),\n el('span', { text: 'page ' + (state.page + 1) + ' / ' + pages + ' · ' + total + ' total' }),\n el('button', { text: 'Next ›', onclick: function () { if (state.page < pages - 1) { state.page++; render(false); } } })\n ]));\n }\n\n // ── Data tab ───────────────────────────────────────────────────────────────\n function renderData(main) {\n if (!state.collection) { main.appendChild(el('div', { class: 'empty', text: 'No collections.' })); return; }\n var searchBox = el('input', { type: 'text', placeholder: 'search…', value: state.search });\n searchBox.oninput = function () { state.search = searchBox.value; state.page = 0; debounceRefresh(); };\n var deletedToggle = el('label', {}, [\n (function () { var cb = el('input', { type: 'checkbox' }); cb.checked = state.includeDeleted;\n cb.onchange = function () { state.includeDeleted = cb.checked; state.page = 0; render(false); }; return cb; })(),\n el('span', { text: 'tombstones' })\n ]);\n toolbar(main, state.collection, [searchBox, deletedToggle]);\n\n var q = '/api/collections/' + state.collection + '/records?limit=' + PAGE +\n '&offset=' + (state.page * PAGE) + '&includeDeleted=' + state.includeDeleted +\n (state.search ? '&search=' + encodeURIComponent(state.search) : '');\n api(q).then(function (data) {\n var info = state.overview.collections.filter(function (c) { return c.name === state.collection; })[0];\n var columns = info ? info.columns : [];\n if (!data.records.length) { main.appendChild(el('div', { class: 'empty', text: 'No records.' })); return; }\n var thead = el('tr', {}, [el('th', { text: 'id' })].concat(\n columns.map(function (c) { return el('th', { text: c }); }),\n [el('th', { text: 'updated' })]\n ));\n var tbody = el('tbody', {}, data.records.map(function (r) {\n return el('tr', { class: 'clickable' + (r.deleted ? ' tombstone' : ''), onclick: function () { openRecord(r.id); } },\n [el('td', { class: 'mono', text: shortId(r.id) })].concat(\n columns.map(function (c) { return el('td', { text: fmtVal(r.fields[c]).slice(0, 60) }); }),\n [el('td', { class: 'mono', text: fmtDateTime(r.updatedAt) })]\n ));\n }));\n main.appendChild(el('table', { class: 'grid' }, [el('thead', {}, [thead]), tbody]));\n pager(main, data.total);\n }).catch(showError(main));\n }\n\n // ── Record drawer ──────────────────────────────────────────────────────────\n function openRecord(id) {\n api('/api/collections/' + state.collection + '/records/' + encodeURIComponent(id)).then(function (data) {\n var drawer = qs('#drawer');\n drawer.textContent = '';\n var r = data.record;\n drawer.appendChild(el('h3', {}, [\n el('span', { class: 'mono', text: r.id }),\n el('span', { class: 'close', text: '✕', onclick: closeDrawer })\n ]));\n if (r.deleted) drawer.appendChild(el('span', { class: 'badge delete', text: 'tombstone' }));\n\n drawer.appendChild(el('h4', { text: 'Fields — with last writer per field' }));\n Object.keys(r.fields).forEach(function (f) {\n var fv = r.fieldVersions[f];\n var val = fmtVal(r.fields[f]);\n var preview = data.richtextPreviews && data.richtextPreviews[f];\n drawer.appendChild(el('div', { class: 'fieldrow' }, [\n el('div', { class: 'fname mono', text: f }),\n el('div', { class: 'fval', text: val }),\n preview ? el('div', { class: 'preview', text: '“' + preview + '”' }) : null,\n fv ? el('span', { class: 'chip' }, [\n el('span', { text: 'last writer' }),\n el('b', { class: 'mono', text: shortNode(fv.nodeId) }),\n el('span', { class: 'mono', text: fmtDateTime(fv.wallTime) })\n ]) : el('span', { class: 'chip', text: 'no field version' })\n ]));\n });\n\n drawer.appendChild(el('h4', { text: 'Causal graph for this record' }));\n var dagBox = el('div', { class: 'dagwrap' });\n drawer.appendChild(dagBox);\n api('/api/collections/' + state.collection + '/dag?record=' + encodeURIComponent(id)).then(function (dag) {\n renderDag(dagBox, dag, 500);\n });\n\n drawer.appendChild(el('h4', { text: 'Operation history (newest first)' }));\n if (!data.operations.length) drawer.appendChild(el('div', { class: 'empty', text: 'No operations (compacted?).' }));\n data.operations.forEach(function (o) {\n var body = el('div', { class: 'op ' + o.type }, [\n el('div', {}, [\n el('span', { class: 'badge ' + o.type, text: o.type }),\n el('span', { class: 'meta', text: ' ' + shortNode(o.nodeId) + ' · seq ' + o.sequenceNumber + ' · ' + (o.timestamp ? fmtDateTime(o.timestamp.wallTime) : '·') })\n ])\n ]);\n if (o.data) {\n var pre = el('pre', { class: 'mono' });\n pre.textContent = JSON.stringify(o.data, null, 1);\n body.appendChild(pre);\n }\n drawer.appendChild(body);\n });\n drawer.className = 'drawer open';\n }).catch(function (e) { alertError(e); });\n }\n function closeDrawer() { qs('#drawer').className = 'drawer'; }\n\n // ── Operations tab (DAG + table) ───────────────────────────────────────────\n function renderOps(main) {\n if (!state.collection) { main.appendChild(el('div', { class: 'empty', text: 'No collections.' })); return; }\n toolbar(main, state.collection + ' — operation log');\n var dagBox = el('div', { class: 'dagwrap' });\n main.appendChild(dagBox);\n api('/api/collections/' + state.collection + '/dag?limit=120').then(function (dag) {\n renderDag(dagBox, dag, null);\n });\n\n api('/api/collections/' + state.collection + '/ops?limit=' + PAGE + '&offset=' + (state.page * PAGE)).then(function (data) {\n if (!data.operations.length) { main.appendChild(el('div', { class: 'empty', text: 'Empty (compacted?).' })); return; }\n var thead = el('tr', {}, ['type', 'record', 'node', 'seq', 'time', 'data'].map(function (h) { return el('th', { text: h }); }));\n var tbody = el('tbody', {}, data.operations.map(function (o) {\n return el('tr', { class: 'clickable', onclick: function () { openRecord(o.recordId); } }, [\n el('td', {}, [el('span', { class: 'badge ' + o.type, text: o.type })]),\n el('td', { class: 'mono', text: shortId(o.recordId) }),\n el('td', { class: 'mono', text: shortNode(o.nodeId) }),\n el('td', { class: 'mono', text: String(o.sequenceNumber) }),\n el('td', { class: 'mono', text: o.timestamp ? fmtDateTime(o.timestamp.wallTime) : '·' }),\n el('td', { class: 'mono', text: o.data ? JSON.stringify(o.data).slice(0, 70) : '·' })\n ]);\n }));\n main.appendChild(el('table', { class: 'grid' }, [el('thead', {}, [thead]), tbody]));\n pager(main, data.total);\n }).catch(showError(main));\n }\n\n // ── Causal DAG renderer (SVG) ──────────────────────────────────────────────\n function renderDag(container, dag, maxWidth) {\n container.textContent = '';\n if (!dag.nodes.length) { container.appendChild(el('div', { class: 'empty', text: 'No operations to graph.' })); return; }\n var XSTEP = 46, YSTEP = 44, PADX = 90, PADY = 26, R = 7;\n var width = PADX + dag.nodes.length * XSTEP + 30;\n var height = PADY + dag.lanes.length * YSTEP + 16;\n var svgns = 'http://www.w3.org/2000/svg';\n var svg = document.createElementNS(svgns, 'svg');\n svg.setAttribute('width', String(width));\n svg.setAttribute('height', String(height));\n\n var defs = document.createElementNS(svgns, 'defs');\n defs.innerHTML = '<marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#3a4356\"/></marker>';\n svg.appendChild(defs);\n\n var colors = { insert: '#48bb78', update: '#4299e1', delete: '#f56565' };\n var pos = {};\n dag.nodes.forEach(function (n) {\n pos[n.id] = { x: PADX + n.x * XSTEP, y: PADY + n.lane * YSTEP + YSTEP / 2 };\n });\n\n dag.lanes.forEach(function (lane, i) {\n var y = PADY + i * YSTEP + YSTEP / 2;\n var line = document.createElementNS(svgns, 'line');\n line.setAttribute('x1', '10'); line.setAttribute('x2', String(width - 10));\n line.setAttribute('y1', String(y)); line.setAttribute('y2', String(y));\n line.setAttribute('class', 'dag-lane-line');\n svg.appendChild(line);\n var label = document.createElementNS(svgns, 'text');\n label.setAttribute('x', '10'); label.setAttribute('y', String(y - 8));\n label.setAttribute('class', 'dag-lane-label');\n label.textContent = lane.shortNodeId;\n svg.appendChild(label);\n });\n\n dag.edges.forEach(function (e) {\n var a = pos[e.from], b = pos[e.to];\n if (!a || !b) return;\n var path = document.createElementNS(svgns, 'path');\n var midX = (a.x + b.x) / 2;\n path.setAttribute('d', 'M ' + a.x + ' ' + a.y + ' C ' + midX + ' ' + a.y + ', ' + midX + ' ' + b.y + ', ' + (b.x - R - 2) + ' ' + b.y);\n path.setAttribute('class', 'dag-edge');\n svg.appendChild(path);\n });\n\n var tip = qs('#dagtip');\n dag.nodes.forEach(function (n) {\n var g = document.createElementNS(svgns, 'g');\n g.setAttribute('class', 'dag-node');\n var c = document.createElementNS(svgns, 'circle');\n var p = pos[n.id];\n c.setAttribute('cx', String(p.x)); c.setAttribute('cy', String(p.y)); c.setAttribute('r', String(R));\n c.setAttribute('fill', '#161a22');\n c.setAttribute('stroke', colors[n.type] || '#8b93a7');\n g.appendChild(c);\n g.addEventListener('mousemove', function (ev) {\n tip.style.display = 'block';\n tip.style.left = (ev.clientX + 14) + 'px';\n tip.style.top = (ev.clientY + 14) + 'px';\n tip.innerHTML = '<b>' + n.type + '</b> by ' + n.shortNodeId + ' · seq ' + n.sequenceNumber +\n '<br/>record ' + shortId(n.recordId) +\n (n.dataPreview ? '<br/><span class=\"mono\">' + escapeHtml(n.dataPreview) + '</span>' : '');\n });\n g.addEventListener('mouseleave', function () { tip.style.display = 'none'; });\n g.addEventListener('click', function () { openRecord(n.recordId); });\n svg.appendChild(g);\n });\n\n if (maxWidth) container.style.maxWidth = maxWidth + 'px';\n container.appendChild(svg);\n container.scrollLeft = width;\n }\n function escapeHtml(s) {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n }\n\n // ── Time travel tab ────────────────────────────────────────────────────────\n function renderTimeTravel(main) {\n if (!state.collection) { main.appendChild(el('div', { class: 'empty', text: 'No collections.' })); return; }\n toolbar(main, state.collection + ' — time travel');\n api('/api/collections/' + state.collection + '/replay').then(function (full) {\n var total = full.totalCount;\n if (!total) { main.appendChild(el('div', { class: 'empty', text: 'No operations to replay.' })); return; }\n if (state.ttPosition === null || state.ttPosition > total) state.ttPosition = total;\n\n var posLabel = el('span', { class: 'mono', text: '' });\n var slider = el('input', { type: 'range', min: '1', max: String(total), value: String(state.ttPosition) });\n var playBtn = el('button', { text: '▶ replay' });\n var cutBox = el('div', { class: 'tt-cut mono' });\n var stateBox = el('div', {});\n\n function show(position) {\n state.ttPosition = position;\n posLabel.textContent = position + ' / ' + total + ' ops';\n api('/api/collections/' + state.collection + '/replay?upTo=' + encodeURIComponent(opIdAt(position))).then(function (cut) {\n var o = cut.cutOperation;\n cutBox.innerHTML = o\n ? 'cut at <b>' + o.type + '</b> by ' + shortNode(o.nodeId) + ' · ' + (o.timestamp ? fmtDateTime(o.timestamp.wallTime) : '') +\n (o.data ? ' · ' + escapeHtml(JSON.stringify(o.data).slice(0, 90)) : '')\n : '';\n renderReplayState(stateBox, cut);\n });\n }\n // The full replay result orders ops by HLC; fetch the ordered id list once.\n var orderedIds = [];\n api('/api/collections/' + state.collection + '/dag?limit=20000').then(function (dag) {\n orderedIds = dag.nodes.map(function (n) { return n.id; });\n show(Math.min(state.ttPosition, orderedIds.length));\n });\n function opIdAt(position) { return orderedIds[position - 1]; }\n\n slider.oninput = function () { show(Number(slider.value)); };\n playBtn.onclick = function () {\n if (state.ttPlaying) { clearInterval(state.ttPlaying); state.ttPlaying = null; playBtn.textContent = '▶ replay'; return; }\n var pos = 1;\n playBtn.textContent = '⏸ pause';\n state.ttPlaying = setInterval(function () {\n if (pos > orderedIds.length) { clearInterval(state.ttPlaying); state.ttPlaying = null; playBtn.textContent = '▶ replay'; return; }\n slider.value = String(pos);\n show(pos);\n pos++;\n }, 350);\n };\n\n main.appendChild(el('div', { class: 'tt-controls' }, [playBtn, slider, posLabel]));\n main.appendChild(cutBox);\n main.appendChild(stateBox);\n }).catch(showError(main));\n }\n\n function renderReplayState(box, cut) {\n box.textContent = '';\n if (!cut.records.length) { box.appendChild(el('div', { class: 'empty', text: 'No records at this point.' })); return; }\n var fieldSet = {};\n cut.records.forEach(function (r) { Object.keys(r.fields).forEach(function (f) { fieldSet[f] = true; }); });\n var fields = Object.keys(fieldSet);\n var thead = el('tr', {}, [el('th', { text: 'id' })].concat(fields.map(function (f) { return el('th', { text: f }); })));\n var tbody = el('tbody', {}, cut.records.map(function (r) {\n return el('tr', { class: r.deleted ? 'tombstone' : '' },\n [el('td', { class: 'mono', text: shortId(r.id) })].concat(fields.map(function (f) {\n return el('td', { text: r.fields[f] === undefined ? '' : fmtVal(r.fields[f]).slice(0, 40) });\n })));\n }));\n box.appendChild(el('table', { class: 'grid' }, [el('thead', {}, [thead]), tbody]));\n }\n\n // ── Merges tab ─────────────────────────────────────────────────────────────\n function renderMerges(main) {\n toolbar(main, 'Merge audit trail');\n api('/api/audit?limit=200').then(function (data) {\n if (!data.traces.length) { main.appendChild(el('div', { class: 'empty', text: 'No merge audit traces recorded.' })); return; }\n var thead = el('tr', {}, ['when', 'event', 'collection', 'record', 'field', 'strategy', 'tier'].map(function (h) { return el('th', { text: h }); }));\n var tbody = el('tbody', {}, data.traces.map(function (t) {\n return el('tr', {}, [\n el('td', { class: 'mono', text: fmtDateTime(t.recordedAt) }),\n el('td', { text: t.eventType }),\n el('td', { text: t.collection }),\n el('td', { class: 'mono', text: shortId(t.recordId) }),\n el('td', { text: t.field }),\n el('td', { text: t.strategy }),\n el('td', { text: String(t.tier) })\n ]);\n }));\n main.appendChild(el('table', { class: 'grid' }, [el('thead', {}, [thead]), tbody]));\n }).catch(showError(main));\n }\n\n // ── Sync tab ───────────────────────────────────────────────────────────────\n function renderSync(main) {\n toolbar(main, 'Sync state' + (state.device ? ' — ' + state.device : ''));\n var o = state.overview;\n main.appendChild(el('div', { class: 'panel' }, [\n el('h3', { text: 'Version vector — what this store has seen from each node' }),\n el('table', { class: 'grid' }, [\n el('thead', {}, [el('tr', {}, [el('th', { text: 'node' }), el('th', { text: 'max sequence' })])]),\n el('tbody', {}, o.versionVector.map(function (v) {\n return el('tr', {}, [el('td', { class: 'mono', text: v.nodeId }), el('td', { class: 'mono', text: String(v.sequenceNumber) })]);\n }))\n ])\n ]));\n main.appendChild(el('div', { class: 'panel' }, [\n el('h3', { text: 'Outbound queue' }),\n el('div', { text: o.pendingSyncOps + ' operation(s) waiting to sync' })\n ]));\n main.appendChild(el('div', { class: 'panel' }, [\n el('h3', { text: 'Store meta' }),\n el('table', { class: 'grid' }, [\n el('tbody', {}, o.meta.map(function (m) {\n return el('tr', {}, [el('td', { class: 'mono', text: m.key }), el('td', { class: 'mono', text: m.value })]);\n }))\n ])\n ]));\n if (state.mode === 'spectator') {\n var feedBox = el('div', { class: 'lab-feed', style: 'width:100%;max-height:340px' }, [\n el('h3', { text: 'Live events from the server' }),\n el('div', { class: 'feed', id: 'feed' })\n ]);\n main.appendChild(feedBox);\n state.feed.slice(-200).forEach(appendFeedLine);\n }\n }\n\n // ── Lab tab ────────────────────────────────────────────────────────────────\n function renderLab(full) {\n var main = qs('#main');\n qs('#sidebar').style.display = 'none';\n Promise.all([fetch('/api/lab/state').then(function (r) { return r.json(); }),\n fetch('/api/lab/convergence').then(function (r) { return r.json(); })])\n .then(function (results) {\n state.labState = results[0];\n state.devices = results[0].devices.map(function (d) { return d.name; });\n var convergence = results[1];\n main.textContent = '';\n qs('#dbpath').textContent = 'sync laboratory · ' + results[0].serverOperations + ' ops on server';\n\n var conv = el('div', { class: 'convergence ' + (convergence.converged ? 'ok' : 'bad') }, [\n el('div', { text: convergence.converged\n ? '✓ ALL ' + convergence.deviceCount + ' DEVICES CONVERGED'\n : '✗ DEVICES DIVERGED (' + convergence.differences.length + ' difference(s)) — sync to converge' })\n ]);\n if (!convergence.converged) {\n conv.appendChild(el('div', { class: 'diffs', text: convergence.differences.slice(0, 4).join(' · ') }));\n }\n var addBtn = el('button', { class: 'primary', text: '+ add device', onclick: function () {\n post('/api/lab/devices', {}).then(function () { render(true); }).catch(alertError);\n } });\n var syncAllBtn = el('button', { text: 'sync all', onclick: function () {\n var chain = Promise.resolve();\n state.devices.forEach(function (d) { chain = chain.then(function () { return post('/api/lab/devices/' + d + '/sync', {}); }); });\n chain.then(function () { return post('/api/lab/devices/' + state.devices[0] + '/sync', {}); })\n .then(function () { render(true); }).catch(alertError);\n } });\n main.appendChild(el('div', { class: 'lab-top' }, [conv, addBtn, syncAllBtn]));\n\n var devicesBox = el('div', { class: 'lab-devices' });\n results[0].devices.forEach(function (d) { devicesBox.appendChild(deviceCard(d, results[0].collections)); });\n\n var feedBox = el('div', { class: 'lab-feed' }, [\n el('h3', { text: 'Live events' }),\n el('div', { class: 'feed', id: 'feed' })\n ]);\n main.appendChild(el('div', { class: 'lab-grid' }, [devicesBox, feedBox]));\n state.feed.slice(-200).forEach(appendFeedLine);\n })\n .catch(showError(qs('#main')));\n }\n\n function deviceCard(d, collections) {\n var collection = collections[0];\n var card = el('div', { class: 'device-card' + (d.connected ? '' : ' offline') });\n card.appendChild(el('div', { class: 'device-head' }, [\n el('span', { class: 'name ' + deviceColor(d.name), text: d.name }),\n el('span', { class: 'node mono', text: shortNode(d.nodeId) }),\n el('span', { class: 'badge ' + (d.connected ? 'ok' : 'bad'), text: d.connected ? 'online' : 'offline' }),\n d.pendingOperations ? el('span', { class: 'badge neutral', text: d.pendingOperations + ' queued' }) : null,\n el('span', { class: 'spacer' }),\n el('button', { text: d.connected ? 'disconnect' : 'connect', onclick: function () {\n post('/api/lab/devices/' + d.name + '/' + (d.connected ? 'disconnect' : 'connect'), {})\n .then(function () { render(true); }).catch(alertError);\n } }),\n el('button', { text: 'sync', onclick: function () {\n post('/api/lab/devices/' + d.name + '/sync', {}).then(function () { render(true); }).catch(alertError);\n } })\n ]));\n\n var body = el('div', { class: 'device-body' });\n card.appendChild(body);\n\n // Chaos controls\n body.appendChild(el('h5', { text: 'Network chaos (applies on next connect)' }));\n var chaosDefs = [['dropRate', 'drop'], ['duplicateRate', 'duplicate'], ['reorderRate', 'reorder']];\n chaosDefs.forEach(function (cd) {\n var key = cd[0];\n var valueLabel = el('span', { class: 'mono', text: String(d.chaos[key]) });\n var range = el('input', { type: 'range', min: '0', max: '0.5', step: '0.05', value: String(d.chaos[key]) });\n range.oninput = function () { valueLabel.textContent = range.value; };\n range.onchange = function () {\n var body2 = {}; body2[key] = Number(range.value);\n post('/api/lab/devices/' + d.name + '/chaos', body2).catch(alertError);\n };\n body.appendChild(el('div', { class: 'chaos-row' }, [el('span', { text: cd[1] }), range, valueLabel]));\n });\n\n // Records with inline editing\n body.appendChild(el('h5', { text: 'records (' + collection.name + ') — edits happen ON this device' }));\n var recBox = el('div', {});\n body.appendChild(recBox);\n api('/api/collections/' + collection.name + '/records?limit=20&device=' + encodeURIComponent(d.name)).then(function (data) {\n data.records.forEach(function (r) {\n var titleInput = el('input', { type: 'text', value: String(r.fields.title || '') });\n titleInput.onchange = function () {\n post('/api/lab/devices/' + d.name + '/update', { collection: collection.name, id: r.id, data: { title: titleInput.value } }).catch(alertError);\n };\n var doneCb = el('input', { type: 'checkbox', title: 'done' });\n doneCb.checked = !!r.fields.done;\n doneCb.onchange = function () {\n post('/api/lab/devices/' + d.name + '/update', { collection: collection.name, id: r.id, data: { done: doneCb.checked } }).catch(alertError);\n };\n var incBtn = el('button', { text: '+1', title: 'atomic increment — composes across devices', onclick: function () {\n post('/api/lab/devices/' + d.name + '/update', { collection: collection.name, id: r.id, data: {}, increments: { points: 1 } })\n .then(function () { debounceRefresh(); }).catch(alertError);\n } });\n var delBtn = el('button', { class: 'danger', text: '✕', title: 'delete', onclick: function () {\n post('/api/lab/devices/' + d.name + '/delete', { collection: collection.name, id: r.id })\n .then(function () { debounceRefresh(); }).catch(alertError);\n } });\n recBox.appendChild(el('div', { class: 'rec-line' }, [\n doneCb, titleInput,\n el('span', { class: 'pts mono', text: String(r.fields.points === undefined ? '' : r.fields.points) }),\n incBtn, delBtn,\n el('button', { text: '⌕', title: 'inspect', onclick: function () { state.device = d.name; state.collection = collection.name; openRecord(r.id); } })\n ]));\n });\n if (!data.records.length) recBox.appendChild(el('div', { class: 'empty', text: 'no records yet' }));\n });\n\n // Insert form\n var newTitle = el('input', { type: 'text', placeholder: 'new task title…' });\n var insertBtn = el('button', { class: 'primary', text: 'insert', onclick: function () {\n if (!newTitle.value.trim()) return;\n post('/api/lab/devices/' + d.name + '/insert', { collection: collection.name, data: { title: newTitle.value.trim() } })\n .then(function () { newTitle.value = ''; debounceRefresh(); }).catch(alertError);\n } });\n newTitle.addEventListener('keydown', function (e) { if (e.key === 'Enter') insertBtn.click(); });\n body.appendChild(el('div', { class: 'insert-form' }, [newTitle, insertBtn]));\n return card;\n }\n\n function appendFeedLine(event) {\n var feed = qs('#feed');\n if (!feed) return;\n var cls = 'feed-line';\n if (event.type === 'operation:created') cls += ' evt-created';\n if (event.type === 'operation:applied') cls += ' evt-applied';\n if (event.type === 'merge:conflict') cls += ' evt-conflict';\n if (event.type === 'sync:apply-failed') cls += ' evt-failed';\n feed.appendChild(el('div', { class: cls }, [\n el('span', { class: 'mono', text: fmtTime(event.at) }),\n el('span', { class: 'dev ' + deviceColor(event.device), text: event.device }),\n el('span', { class: 'what', text: event.type.replace('operation:', '').replace('sync:', '') + ' · ' + event.summary })\n ]));\n feed.scrollTop = feed.scrollHeight;\n }\n\n // ── Misc ───────────────────────────────────────────────────────────────────\n function showError(main) {\n return function (e) { main.appendChild(el('div', { class: 'error', text: e.message })); };\n }\n function alertError(e) { console.error(e); qs('#dbpath').textContent = 'error: ' + e.message; }\n\n qs('#refresh').onclick = function () { render(true); };\n document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeDrawer(); });\n})();\n`\n","/** Kora Studio stylesheet (served at /style.css). */\nexport const STUDIO_CSS = `\n:root {\n --bg: #0e1015; --panel: #161a22; --panel-2: #1c212c; --panel-3: #232936;\n --border: #262c3a; --text: #e6e9f0; --muted: #8b93a7;\n --accent: #4fd1c5; --accent-2: #9f7aea;\n --insert: #48bb78; --update: #4299e1; --delete: #f56565; --warn: #ed8936;\n --ok-bg: rgba(72,187,120,.12); --bad-bg: rgba(245,101,101,.12);\n}\n* { box-sizing: border-box; margin: 0; padding: 0; }\nhtml, body { height: 100%; }\nbody {\n background: var(--bg); color: var(--text); overflow: hidden;\n font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n}\n.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }\nbutton {\n background: var(--panel-2); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 5px 12px; cursor: pointer; font-size: 13px;\n}\nbutton:hover { border-color: var(--accent); }\nbutton.primary { background: rgba(79,209,197,.12); border-color: var(--accent); color: var(--accent); }\nbutton.danger { color: var(--delete); }\nbutton:disabled { opacity: .4; cursor: default; }\ninput, select {\n background: var(--bg); color: var(--text); border: 1px solid var(--border);\n border-radius: 6px; padding: 4px 8px; font-size: 13px;\n}\ninput:focus, select:focus { outline: none; border-color: var(--accent); }\ninput[type=\"checkbox\"] { width: auto; }\ninput[type=\"range\"] { padding: 0; }\n\nheader {\n display: flex; align-items: center; gap: 14px; padding: 10px 16px;\n background: var(--panel); border-bottom: 1px solid var(--border); height: 48px;\n}\nheader .logo { font-weight: 700; letter-spacing: .3px; white-space: nowrap; }\nheader .logo span { color: var(--accent); }\nheader .modebadge {\n font-size: 10px; text-transform: uppercase; letter-spacing: 1px; padding: 2px 8px;\n border-radius: 10px; background: rgba(159,122,234,.15); color: var(--accent-2);\n}\nheader .dbpath { color: var(--muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }\nheader .live { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 12px; }\nheader .live .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }\nheader .live.on .dot { background: var(--insert); box-shadow: 0 0 8px rgba(72,187,120,.8); }\n\n.tabs { display: flex; gap: 2px; padding: 0 16px; background: var(--panel); border-bottom: 1px solid var(--border); }\n.tabs .tab {\n padding: 8px 16px; cursor: pointer; color: var(--muted); font-size: 13px;\n border-bottom: 2px solid transparent; user-select: none;\n}\n.tabs .tab:hover { color: var(--text); }\n.tabs .tab.active { color: var(--accent); border-bottom-color: var(--accent); }\n.tabs .spacer { flex: 1; }\n.tabs .devicepick { display: flex; align-items: center; gap: 6px; padding: 6px 0; color: var(--muted); font-size: 12px; }\n\n.layout { display: flex; height: calc(100vh - 85px); }\nnav.sidebar {\n width: 220px; background: var(--panel); border-right: 1px solid var(--border);\n padding: 12px 0; overflow-y: auto; flex-shrink: 0;\n}\nnav.sidebar h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); padding: 8px 16px 4px; }\nnav.sidebar .item {\n display: flex; justify-content: space-between; align-items: center;\n padding: 6px 16px; cursor: pointer; border-left: 2px solid transparent; font-size: 13px;\n}\nnav.sidebar .item:hover { background: var(--panel-2); }\nnav.sidebar .item.active { border-left-color: var(--accent); background: var(--panel-2); }\nnav.sidebar .item .count { color: var(--muted); font-size: 11px; }\nmain { flex: 1; overflow: auto; padding: 16px 20px; }\n\n.toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }\n.toolbar h2 { font-size: 16px; margin-right: auto; }\n.toolbar label { color: var(--muted); font-size: 12px; display: flex; align-items: center; gap: 5px; cursor: pointer; }\n\ntable.grid { width: 100%; border-collapse: collapse; background: var(--panel); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }\ntable.grid th, table.grid td { text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }\ntable.grid th { background: var(--panel-2); color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .5px; position: sticky; top: 0; z-index: 1; }\ntable.grid tbody tr.clickable { cursor: pointer; }\ntable.grid tbody tr.clickable:hover { background: var(--panel-2); }\ntr.tombstone td { color: var(--muted); text-decoration: line-through; }\n\n.badge { display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; }\n.badge.insert { background: rgba(72,187,120,.15); color: var(--insert); }\n.badge.update { background: rgba(66,153,225,.15); color: var(--update); }\n.badge.delete { background: rgba(245,101,101,.15); color: var(--delete); }\n.badge.neutral { background: var(--panel-3); color: var(--muted); }\n.badge.ok { background: var(--ok-bg); color: var(--insert); }\n.badge.bad { background: var(--bad-bg); color: var(--delete); }\n\n.chip {\n display: inline-flex; gap: 6px; align-items: center; background: var(--panel-2);\n border: 1px solid var(--border); border-radius: 6px; padding: 2px 8px; font-size: 11px; color: var(--muted);\n}\n.chip b { color: var(--accent-2); font-weight: 600; }\n\n.pager { display: flex; gap: 8px; align-items: center; margin-top: 10px; color: var(--muted); font-size: 12px; }\n.empty { color: var(--muted); padding: 40px; text-align: center; }\n.error { color: var(--delete); padding: 20px; }\n\n.drawer {\n position: fixed; top: 85px; right: 0; bottom: 0; width: 560px; max-width: 92vw;\n background: var(--panel); border-left: 1px solid var(--border); overflow-y: auto;\n padding: 16px; box-shadow: -12px 0 30px rgba(0,0,0,.45); display: none; z-index: 20;\n}\n.drawer.open { display: block; }\n.drawer h3 { font-size: 14px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; }\n.drawer h4 { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; margin: 16px 0 8px; }\n.drawer .close { cursor: pointer; color: var(--muted); padding: 4px 8px; }\n.fieldrow { padding: 8px 10px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 6px; }\n.fieldrow .fname { color: var(--accent); font-size: 12px; }\n.fieldrow .fval { word-break: break-word; margin: 2px 0 4px; }\n.fieldrow .preview { color: var(--muted); font-style: italic; }\n\n.op { border-left: 2px solid var(--border); margin-left: 6px; padding: 6px 0 6px 14px; position: relative; }\n.op::before { content: \"\"; position: absolute; left: -5px; top: 13px; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }\n.op.insert::before { background: var(--insert); } .op.update::before { background: var(--update); } .op.delete::before { background: var(--delete); }\n.op .meta { color: var(--muted); font-size: 11px; }\n.op pre { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px; margin-top: 4px; overflow-x: auto; }\n\n.panel { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 14px; margin-bottom: 14px; }\n.panel h3 { font-size: 13px; margin-bottom: 8px; color: var(--accent); }\n\n/* DAG */\n.dagwrap { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; overflow-x: auto; margin-bottom: 14px; }\n.dagwrap svg { display: block; }\n.dag-node { cursor: pointer; }\n.dag-node circle { stroke-width: 2; }\n.dag-node text { fill: var(--muted); font-size: 10px; font-family: ui-monospace, Menlo, monospace; }\n.dag-edge { stroke: #3a4356; stroke-width: 1.4; fill: none; marker-end: url(#arrow); }\n.dag-lane-label { fill: var(--accent-2); font-size: 11px; font-family: ui-monospace, Menlo, monospace; }\n.dag-lane-line { stroke: #1d2330; stroke-width: 1; }\n.dag-tip {\n position: fixed; background: var(--panel-3); border: 1px solid var(--border); border-radius: 6px;\n padding: 6px 10px; font-size: 12px; pointer-events: none; z-index: 50; max-width: 380px; display: none;\n}\n\n/* Time travel */\n.tt-controls { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }\n.tt-controls input[type=\"range\"] { flex: 1; }\n.tt-cut { background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-bottom: 14px; font-size: 12px; }\n\n/* Lab */\n.lab-top { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; flex-wrap: wrap; }\n.convergence { padding: 8px 14px; border-radius: 8px; font-weight: 600; font-size: 13px; }\n.convergence.ok { background: var(--ok-bg); color: var(--insert); border: 1px solid rgba(72,187,120,.4); }\n.convergence.bad { background: var(--bad-bg); color: var(--delete); border: 1px solid rgba(245,101,101,.4); }\n.convergence .diffs { font-weight: 400; font-size: 11px; margin-top: 4px; color: var(--muted); }\n.lab-grid { display: flex; gap: 14px; align-items: flex-start; }\n.lab-devices { flex: 1; display: grid; grid-template-columns: repeat(auto-fill, minmax(390px, 1fr)); gap: 14px; }\n.device-card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }\n.device-card.offline { border-color: rgba(245,101,101,.35); }\n.device-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: var(--panel-2); }\n.device-head .name { font-weight: 700; }\n.device-head .node { color: var(--muted); font-size: 11px; }\n.device-head .spacer { flex: 1; }\n.device-body { padding: 10px 12px; }\n.device-body h5 { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; margin: 10px 0 6px; }\n.chaos-row { display: grid; grid-template-columns: 78px 1fr 44px; gap: 6px; align-items: center; font-size: 11px; color: var(--muted); margin-bottom: 3px; }\n.rec-line { display: flex; align-items: center; gap: 6px; padding: 5px 6px; border-bottom: 1px solid var(--border); font-size: 12px; flex-wrap: wrap; }\n.rec-line:hover { background: var(--panel-2); }\n.rec-line .title { flex: 1; min-width: 120px; }\n.rec-line input[type=\"text\"] { font-size: 12px; padding: 2px 6px; width: 130px; }\n.rec-line .pts { color: var(--accent); min-width: 30px; text-align: right; }\n.insert-form { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }\n.insert-form input { flex: 1; min-width: 120px; }\n.lab-feed { width: 360px; flex-shrink: 0; background: var(--panel); border: 1px solid var(--border); border-radius: 10px; display: flex; flex-direction: column; max-height: calc(100vh - 200px); }\n.lab-feed h3 { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; padding: 10px 12px; border-bottom: 1px solid var(--border); }\n.lab-feed .feed { overflow-y: auto; padding: 6px 0; flex: 1; }\n.feed-line { display: flex; gap: 8px; padding: 3px 12px; font-size: 11px; align-items: baseline; }\n.feed-line .dev { min-width: 66px; font-weight: 600; }\n.feed-line .what { color: var(--muted); }\n.feed-line.evt-created .what, .feed-line.evt-applied .what { color: var(--text); }\n.feed-line.evt-conflict .what { color: var(--warn); }\n.feed-line.evt-failed .what { color: var(--delete); font-weight: 700; }\n.dev-c0 { color: #4fd1c5; } .dev-c1 { color: #9f7aea; } .dev-c2 { color: #f6ad55; }\n.dev-c3 { color: #63b3ed; } .dev-c4 { color: #f687b3; } .dev-c5 { color: #68d391; }\n`\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAtB,IAAM,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;A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","names":[]}
|