@telepath-computer/television 0.1.1 → 0.1.3

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.
@@ -0,0 +1 @@
1
+ artifact-view{background:var(--color-bg-muted);border:1px solid var(--color-border);border-radius:10px;box-sizing:border-box;width:100%;height:100%;display:flex;flex-direction:column;overflow:hidden}.artifact-title-bar{display:flex;align-items:center;height:34px;padding:0 12px;flex-shrink:0;border-bottom:1px solid var(--color-border);-webkit-user-select:none;user-select:none}.artifact-title{flex:1;font-size:13px;font-weight:500;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.artifact-close-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--color-text-muted);cursor:pointer;flex-shrink:0}.artifact-close-btn:hover{background:var(--color-interactive);color:var(--color-text)}.artifact-content{flex:1;min-height:0;overflow-y:auto;padding:16px 20px}.markdown-body{font-size:14px;line-height:1.6;color:var(--color-text)}.markdown-body>:first-child{margin-top:0}.markdown-body>:last-child{margin-bottom:0}.markdown-body h1,.markdown-body h2,.markdown-body h3{font-weight:600;margin:1.25em 0 .5em}.markdown-body h1{font-size:1.4em}.markdown-body h2{font-size:1.2em}.markdown-body h3{font-size:1.05em}.markdown-body p{margin:.75em 0}.markdown-body code{font-family:var(--font-mono);font-size:.9em;background:var(--color-interactive);padding:.15em .35em;border-radius:4px}.markdown-body pre{background:var(--color-interactive);border-radius:6px;padding:12px 14px;overflow-x:auto}.markdown-body pre code{background:none;padding:0}.markdown-body ul,.markdown-body ol{padding-left:1.5em;margin:.75em 0}.markdown-body blockquote{margin:.75em 0;padding-left:1em;border-left:3px solid var(--color-border);color:var(--color-text-muted)}.markdown-body a{color:var(--color-text);text-decoration:underline}.markdown-body hr{border:none;border-top:1px solid var(--color-border);margin:1em 0}layout-stream{width:100%;flex:1;min-width:0;min-height:0;display:flex;align-items:center;overflow:hidden;outline:none;position:relative;--layout-col-width: 300px;--layout-row-height: 133.33px;--layout-gap: 16px}layout-stream .track{display:flex;align-items:start;gap:var(--layout-gap);flex-shrink:0;width:max-content;will-change:transform;transition:transform .2s ease}layout-stream .band{display:grid;grid-template-rows:repeat(6,var(--layout-row-height));gap:var(--layout-gap);align-self:start}layout-stream .band[layout-band-cols="1"]{grid-template-columns:var(--layout-col-width);width:var(--layout-col-width)}layout-stream .band[layout-band-cols="2"]{grid-template-columns:repeat(2,var(--layout-col-width));width:calc(var(--layout-col-width) * 2 + var(--layout-gap))}layout-stream .card-slot{border-radius:12px;box-sizing:border-box;cursor:pointer;min-width:0;min-height:0}layout-stream .card-slot.placeholder{background:#94a3b829;border:2px dashed rgba(148,163,184,.7);box-shadow:inset 0 0 0 1px #0f172a0d}layout-stream .card-slot.dragging{position:fixed;z-index:100;opacity:.7;transform:rotate(2deg);pointer-events:none;cursor:grabbing}layout-stream .card-slot artifact-view{width:100%;height:100%}workspace-view{width:100%;flex:1;min-width:0;min-height:0;display:flex;flex-direction:column}workspace-view [data-testid=empty-workspace]{margin:0;padding:1rem}television-app{width:100%;height:100%;display:grid;grid-template-rows:auto 1fr;grid-template-areas:"header" "main"}television-app>header{grid-area:header}television-app>main{grid-area:main;grid-row:1 / -1}body.electron television-app>header{-webkit-app-region:drag;padding-left:80px;border-bottom:1px solid var(--color-border)}header{display:flex;align-items:center;justify-content:space-between;height:34px;padding:0 12px;flex-shrink:0;-webkit-user-select:none;user-select:none}television-app>header>nav{display:flex;align-items:center;gap:8px;min-width:0}workspace-picker,workspace-picker [trigger],button[variant=toolbar],.settings-server-remove-button,.settings-submit-button,.settings-cancel-button,.settings-connect-button,.settings-field input{-webkit-app-region:no-drag}workspace-picker dropdown-menu::part(panel){min-width:18rem}workspace-picker [trigger],workspace-picker [trigger][data-dropdown-trigger]{min-width:0;padding:0;border:none;background:transparent;font-size:13px;font-weight:600}workspace-picker [trigger] .trigger-label{white-space:nowrap}dropdown-group{display:block;padding:4px 0}dropdown-label{display:block;padding:0 10px 4px;font-size:12px;color:color-mix(in srgb,currentColor 60%,transparent)}dropdown-item{display:block;padding:6px 10px;border-radius:4px;cursor:pointer}dropdown-item[selected]{background:color-mix(in srgb,var(--color-border) 70%,white)}dropdown-item[action]{color:color-mix(in srgb,currentColor 50%,transparent)}dropdown-item[danger]{color:#c74343}television-app>main{min-width:0;min-height:0;overflow:hidden;display:flex;flex-direction:column}television-app>main.auth-gate{display:flex;align-items:center;justify-content:center}.settings-panel{width:min(32rem,100%);display:flex;flex-direction:column;padding:20px;border:1px solid var(--color-border);border-radius:12px;background:var(--color-bg)}.workspace-modal,.auth-modal{width:min(24rem,100%);display:flex;flex-direction:column;padding:20px;border:1px solid var(--color-border);border-radius:12px;background:var(--color-bg)}.auth-form{display:flex;flex-direction:column;gap:12px}.auth-modal-title,.auth-modal-copy{margin:0}.auth-token-input-invalid{border-color:#c74343}.workspace-form{display:flex;flex-direction:column;gap:12px}.workspace-form-actions{display:flex;gap:8px}.workspace-modal-title,.workspace-delete-copy,.workspace-delete-warning,.workspace-delete-error{margin:0}.workspace-delete-warning,.workspace-delete-error{font-size:13px}.workspace-delete-error{color:#c74343}.settings-section-title{margin:0}.settings-section,.settings-server-list,.settings-add-server-form{display:flex;flex-direction:column;gap:12px}.settings-form-actions{display:flex;gap:8px}.settings-card{padding:12px;border:1px solid var(--color-border);border-radius:8px}.settings-server{display:flex;align-items:center;justify-content:space-between;gap:12px}.settings-server-details{min-width:0}.settings-server-name{font-weight:600}.settings-server-url{font-size:13px;word-break:break-word;color:color-mix(in srgb,currentColor 70%,transparent)}.settings-server-remove-button,.settings-submit-button,.settings-cancel-button,.settings-connect-button{padding:8px 12px;border-radius:8px}.settings-field{display:flex;flex-direction:column;gap:6px}.settings-field input{width:100%;padding:8px 10px;border:1px solid var(--color-border);border-radius:8px;background:var(--color-bg);color:inherit;font:inherit;box-sizing:border-box}.settings-submit-button,.settings-cancel-button,.settings-connect-button{align-self:flex-start}:root{--black: #000000;--white: #ffffff;--neutral-50: oklch(.985 .001 106.423);--neutral-100: oklch(.97 .001 106.424);--neutral-200: oklch(.923 .003 48.717);--neutral-300: oklch(.869 .005 56.366);--neutral-400: oklch(.709 .01 56.259);--neutral-500: oklch(.553 .013 58.071);--neutral-600: oklch(.444 .011 73.639);--neutral-700: oklch(.374 .01 67.558);--neutral-800: oklch(.268 .007 34.298);--neutral-900: oklch(.216 .006 56.043);--neutral-950: oklch(.147 .004 49.25)}dropdown-menu{position:relative;display:inline-block;color:inherit}dropdown-group{display:grid;gap:var(--space-4)}dropdown-label{display:block;padding:var(--space-4) var(--space-8);color:var(--color-text-muted);font:inherit;font-size:var(--text-sm);font-weight:600;letter-spacing:.04em;text-transform:uppercase}dropdown-divider{display:block;height:1px;margin:var(--space-4) 0;background:var(--color-border)}[data-dropdown-trigger],dropdown-item{color:inherit;font:inherit;line-height:1.2}[data-dropdown-trigger],dropdown-menu::part(trigger){display:inline-flex;align-items:center;gap:var(--space-8);margin:0;padding:.375rem .5rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);box-shadow:none;cursor:pointer}[data-dropdown-trigger]:hover,dropdown-menu::part(trigger):hover{background:var(--color-interactive-hover)}dropdown-menu[open] [data-dropdown-trigger],dropdown-menu[open]::part(trigger){background:var(--color-interactive)}[data-dropdown-trigger-label],dropdown-menu::part(trigger-label){min-width:0}[data-dropdown-trigger-icon],dropdown-menu::part(trigger-icon){display:inline-flex;align-items:center;justify-content:center;flex:none}[data-dropdown-trigger]:focus-visible,dropdown-menu::part(trigger):focus-visible,dropdown-item:focus-visible{outline:2px solid var(--color-focus-ring);outline-offset:2px}dropdown-menu::part(panel){position:absolute;top:calc(100% + var(--space-4));left:0;z-index:var(--layer-popover);min-width:12rem;padding:var(--space-4);border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);color:var(--color-text);box-shadow:var(--shadow-popover)}dropdown-item{display:block;margin:0;padding:.375rem .5rem;border-radius:var(--radius-sm);cursor:pointer;-webkit-user-select:none;user-select:none}dropdown-item:hover{background:var(--color-interactive-hover)}dropdown-item[selected]{background:var(--color-interactive)}panel-view{display:block}panel-view::part(panel){display:flex;flex-direction:column;gap:var(--space-16);color:var(--color-text)}panel-view::part(header){display:flex;align-items:center;justify-content:space-between;gap:var(--space-12)}panel-view::part(title-group){display:flex;align-items:center;gap:var(--space-8);min-width:0}panel-view::part(title){margin:0;font:inherit;font-size:var(--text-lg);font-weight:600;color:var(--color-text)}panel-view::part(button){display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;margin:0;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);box-shadow:none;cursor:pointer}panel-view::part(button):hover{background:var(--color-interactive-hover)}panel-view::part(button):focus-visible{outline:2px solid var(--color-focus-ring);outline-offset:2px}panel-view::part(icon){display:inline-flex;align-items:center;justify-content:center;flex:none}panel-view::part(back-icon){transform:rotate(90deg)}modal-overlay{color:inherit}modal-overlay::part(backdrop){position:fixed;top:0;right:0;bottom:0;left:0;z-index:var(--layer-modal);display:flex;align-items:center;justify-content:center;padding:var(--space-16);background:var(--color-overlay-backdrop)}*,*:before,*:after{box-sizing:border-box}:root{--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;--font-brand: var(--font-sans);--font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, monospace;--text-sm: 12px;--text-base: 14px;--text-lg: 16px;--leading-base: 1.5;--space-2: 2px;--space-4: 4px;--space-6: 6px;--space-8: 8px;--space-12: 12px;--space-16: 16px;--radius-sm: .375rem;--radius-md: .5rem;--layer-popover: 10;--layer-modal: 1000;--shadow-popover: 0 12px 32px rgb(0 0 0 / .14);--shadow-modal: 0 24px 64px rgb(0 0 0 / .2);--color-bg: var(--neutral-50);--color-bg-muted: var(--neutral-100);--color-surface: var(--white);--color-surface-muted: var(--neutral-100);--color-interactive: var(--neutral-200);--color-interactive-hover: var(--neutral-300);--color-border: var(--neutral-300);--color-border-muted: var(--neutral-200);--color-text: var(--neutral-900);--color-text-muted: var(--neutral-500);--color-link: inherit;--color-focus-ring: var(--neutral-500);--color-overlay-backdrop: rgb(0 0 0 / .5)}html,body{margin:0;padding:0;width:100%;height:100%}html{color-scheme:light}body{font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--color-text);background:var(--color-bg)}a{text-underline-offset:.15em;text-decoration-thickness:.08em}button,input,textarea,select{font:inherit;color:inherit}a{color:var(--color-link)}code,pre{font-family:var(--font-mono)}h1,h2,h3,h4,h5,h6{color:var(--color-text)}[data-ui-icon]{display:inline-block;vertical-align:middle}button{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-6);border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:inherit;cursor:pointer}button:hover{background:var(--color-interactive)}button[variant=toolbar]{border:none;background:transparent;color:var(--color-text-muted)}button[size=sm]{min-height:26px;padding:var(--space-2) var(--space-6)}button[size=md]{min-height:32px;padding:var(--space-6) var(--space-12)}:root{--color-bg: var(--neutral-200);--color-border: var(--neutral-300)}
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Television</title>
7
- <script type="module" crossorigin src="./assets/index-C0a55XYe.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-C0onxKEP.css">
7
+ <script type="module" crossorigin src="./assets/index-Ctq-tqkf.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-W7D1rDld.css">
9
9
  </head>
10
10
  <body></body>
11
11
  </html>
package/dist/cli.cjs CHANGED
@@ -75157,7 +75157,9 @@ function isClientMessage(value) {
75157
75157
  case "create-workspace":
75158
75158
  return typeof message.id === "string" && typeof message.name === "string";
75159
75159
  case "update-workspace":
75160
- return typeof message.workspaceID === "string" && typeof message.fields === "object" && message.fields !== null && (!("name" in message.fields) || typeof message.fields.name === "string");
75160
+ return typeof message.workspaceID === "string" && typeof message.fields === "object" && message.fields !== null && (!("name" in message.fields) || typeof message.fields.name === "string") && (!("layout" in message.fields) || Array.isArray(message.fields.layout) && message.fields.layout.every(
75161
+ (card) => typeof card === "object" && card !== null && "type" in card && card.type === "artifact" && "artifactID" in card && typeof card.artifactID === "string" && "cols" in card && typeof card.cols === "number" && "rows" in card && typeof card.rows === "number"
75162
+ ));
75161
75163
  case "delete-workspace":
75162
75164
  return typeof message.workspaceID === "string";
75163
75165
  default:
@@ -75315,8 +75317,83 @@ function ulid3(seedTime, prng) {
75315
75317
  var import_node_crypto2 = require("node:crypto");
75316
75318
  var import_node_fs2 = require("node:fs");
75317
75319
  var import_node_path3 = __toESM(require("node:path"), 1);
75320
+
75321
+ // src/types.ts
75322
+ var DEFAULT_LAYOUT_CARD_COLS = 2;
75323
+ var DEFAULT_LAYOUT_CARD_ROWS = 6;
75324
+ var LAYOUT_COLS_NARROW = 1;
75325
+ var LAYOUT_ROW_SHORT = 2;
75326
+ var LAYOUT_ROW_MID = 3;
75327
+ var LAYOUT_ROW_TALL = 4;
75328
+ var ALLOWED_COLS = [LAYOUT_COLS_NARROW, DEFAULT_LAYOUT_CARD_COLS];
75329
+ var ALLOWED_ROWS = [
75330
+ LAYOUT_ROW_SHORT,
75331
+ LAYOUT_ROW_MID,
75332
+ LAYOUT_ROW_TALL,
75333
+ DEFAULT_LAYOUT_CARD_ROWS
75334
+ ];
75335
+ function clampLayoutCols(cols) {
75336
+ const n = Math.round(cols);
75337
+ return ALLOWED_COLS.includes(n) ? n : DEFAULT_LAYOUT_CARD_COLS;
75338
+ }
75339
+ function clampLayoutRows(rows) {
75340
+ const n = Math.round(rows);
75341
+ return ALLOWED_ROWS.includes(n) ? n : DEFAULT_LAYOUT_CARD_ROWS;
75342
+ }
75343
+ function createLayoutCardRecord(artifactID) {
75344
+ return {
75345
+ type: "artifact",
75346
+ artifactID,
75347
+ cols: DEFAULT_LAYOUT_CARD_COLS,
75348
+ rows: DEFAULT_LAYOUT_CARD_ROWS
75349
+ };
75350
+ }
75351
+ function normalizeLayoutCardRecord(card) {
75352
+ return {
75353
+ type: "artifact",
75354
+ artifactID: card.artifactID,
75355
+ cols: clampLayoutCols(card.cols ?? DEFAULT_LAYOUT_CARD_COLS),
75356
+ rows: clampLayoutRows(card.rows ?? DEFAULT_LAYOUT_CARD_ROWS)
75357
+ };
75358
+ }
75359
+ function normalizeWorkspace(workspace) {
75360
+ const layout = Array.isArray(workspace.layout) ? workspace.layout.map((card) => normalizeLayoutCardRecord(card)) : [];
75361
+ return {
75362
+ id: workspace.id,
75363
+ name: workspace.name,
75364
+ layout
75365
+ };
75366
+ }
75367
+ function getWorkspaceArtifactIDs(workspace) {
75368
+ return workspace.layout.map((card) => card.artifactID);
75369
+ }
75370
+
75371
+ // src/server/workspace-store.ts
75318
75372
  var JSON_INDENT_SPACES = 2;
75319
75373
  var AUTH_TOKEN_BYTES = 32;
75374
+ var DEFAULT_COLS = 2;
75375
+ var DEFAULT_ROWS = 6;
75376
+ function migrateWorkspaceJson(raw) {
75377
+ if ("layout" in raw && Array.isArray(raw.layout) && raw.layout.length > 0) {
75378
+ return false;
75379
+ }
75380
+ if ("artifacts" in raw && Array.isArray(raw.artifacts)) {
75381
+ raw.layout = raw.artifacts;
75382
+ delete raw.artifacts;
75383
+ return true;
75384
+ }
75385
+ if ("artifactIDs" in raw && Array.isArray(raw.artifactIDs)) {
75386
+ raw.layout = raw.artifactIDs.map((id) => ({
75387
+ type: "artifact",
75388
+ artifactID: id,
75389
+ cols: DEFAULT_COLS,
75390
+ rows: DEFAULT_ROWS
75391
+ }));
75392
+ delete raw.artifactIDs;
75393
+ return true;
75394
+ }
75395
+ return false;
75396
+ }
75320
75397
  var WorkspaceStore = class extends Emitter {
75321
75398
  dataDir;
75322
75399
  workspaces = /* @__PURE__ */ new Map();
@@ -75343,10 +75420,12 @@ var WorkspaceStore = class extends Emitter {
75343
75420
  if (!workspace) {
75344
75421
  throw new Error(`Workspace not found: ${workspaceID}`);
75345
75422
  }
75346
- return workspace.artifactIDs.map((artifactID) => this.artifacts.get(artifactID)).filter((artifact) => artifact !== void 0);
75423
+ return getWorkspaceArtifactIDs(workspace).map((artifactID) => this.artifacts.get(artifactID)).filter((artifact) => artifact !== void 0);
75347
75424
  }
75348
75425
  findArtifactWorkspaces(artifactID) {
75349
- return [...this.workspaces.values()].filter((workspace) => workspace.artifactIDs.includes(artifactID));
75426
+ return [...this.workspaces.values()].filter(
75427
+ (workspace) => workspace.layout.some((card) => card.artifactID === artifactID)
75428
+ );
75350
75429
  }
75351
75430
  createArtifact(input) {
75352
75431
  if (!input.workspaceID) {
@@ -75360,7 +75439,7 @@ var WorkspaceStore = class extends Emitter {
75360
75439
  content: input.content
75361
75440
  };
75362
75441
  this.artifacts.set(artifact.id, artifact);
75363
- workspace.artifactIDs.push(artifact.id);
75442
+ workspace.layout.push(createLayoutCardRecord(artifact.id));
75364
75443
  this.persistArtifact(artifact);
75365
75444
  this.persistWorkspace(workspace);
75366
75445
  this.emit("mutation", {
@@ -75393,11 +75472,11 @@ var WorkspaceStore = class extends Emitter {
75393
75472
  throw new Error(`Artifact not found: ${input.artifactID}`);
75394
75473
  }
75395
75474
  const workspace = this.requireWorkspace(input.workspaceID);
75396
- if (!workspace.artifactIDs.includes(input.artifactID)) {
75475
+ if (!workspace.layout.some((card) => card.artifactID === input.artifactID)) {
75397
75476
  throw new Error(`Artifact not linked to workspace: ${input.artifactID}`);
75398
75477
  }
75399
75478
  this.artifacts.delete(input.artifactID);
75400
- workspace.artifactIDs = workspace.artifactIDs.filter((artifactID) => artifactID !== input.artifactID);
75479
+ workspace.layout = workspace.layout.filter((card) => card.artifactID !== input.artifactID);
75401
75480
  this.persistWorkspace(workspace);
75402
75481
  this.deleteArtifactFile(input.artifactID);
75403
75482
  this.emit("mutation", {
@@ -75410,7 +75489,7 @@ var WorkspaceStore = class extends Emitter {
75410
75489
  const workspace = {
75411
75490
  id: input.id ?? ulid3(),
75412
75491
  name: input.name,
75413
- artifactIDs: []
75492
+ layout: []
75414
75493
  };
75415
75494
  this.workspaces.set(workspace.id, workspace);
75416
75495
  this.persistWorkspace(workspace);
@@ -75422,18 +75501,26 @@ var WorkspaceStore = class extends Emitter {
75422
75501
  }
75423
75502
  updateWorkspace(input) {
75424
75503
  const workspace = this.requireWorkspace(input.workspaceID);
75425
- Object.assign(workspace, input.fields);
75504
+ const nextFields = {};
75505
+ if (typeof input.fields.name === "string") {
75506
+ workspace.name = input.fields.name;
75507
+ nextFields.name = input.fields.name;
75508
+ }
75509
+ if (Array.isArray(input.fields.layout)) {
75510
+ workspace.layout = input.fields.layout.map((card) => normalizeLayoutCardRecord(card));
75511
+ nextFields.layout = workspace.layout.map((card) => normalizeLayoutCardRecord(card));
75512
+ }
75426
75513
  this.persistWorkspace(workspace);
75427
75514
  this.emit("mutation", {
75428
75515
  type: "workspace-updated",
75429
75516
  workspaceID: input.workspaceID,
75430
- fields: input.fields
75517
+ fields: nextFields
75431
75518
  });
75432
75519
  }
75433
75520
  removeWorkspace(input) {
75434
75521
  const workspace = this.requireWorkspace(input.workspaceID);
75435
75522
  this.workspaces.delete(input.workspaceID);
75436
- for (const artifactID of workspace.artifactIDs) {
75523
+ for (const artifactID of getWorkspaceArtifactIDs(workspace)) {
75437
75524
  if (this.findArtifactWorkspaces(artifactID).length > 0) {
75438
75525
  continue;
75439
75526
  }
@@ -75455,9 +75542,15 @@ var WorkspaceStore = class extends Emitter {
75455
75542
  }
75456
75543
  load() {
75457
75544
  for (const file2 of this.readEntityFiles(this.workspacesDir)) {
75458
- const workspace = this.readJsonFile(import_node_path3.default.join(this.workspacesDir, file2));
75459
- if (!workspace) continue;
75545
+ const filePath = import_node_path3.default.join(this.workspacesDir, file2);
75546
+ const raw = this.readJsonFile(filePath);
75547
+ if (!raw || typeof raw !== "object") continue;
75548
+ const migrated = migrateWorkspaceJson(raw);
75549
+ const workspace = normalizeWorkspace(raw);
75460
75550
  this.workspaces.set(workspace.id, workspace);
75551
+ if (migrated) {
75552
+ this.persistWorkspace(workspace);
75553
+ }
75461
75554
  }
75462
75555
  for (const file2 of this.readEntityFiles(this.artifactsDir)) {
75463
75556
  const artifact = this.readJsonFile(import_node_path3.default.join(this.artifactsDir, file2));
@@ -75469,7 +75562,7 @@ var WorkspaceStore = class extends Emitter {
75469
75562
  const workspace = {
75470
75563
  id: ulid3(),
75471
75564
  name: "Default",
75472
- artifactIDs: []
75565
+ layout: []
75473
75566
  };
75474
75567
  this.workspaces.set(workspace.id, workspace);
75475
75568
  this.persistWorkspace(workspace);
package/dist/electron.cjs CHANGED
@@ -71446,7 +71446,9 @@ function isClientMessage(value) {
71446
71446
  case "create-workspace":
71447
71447
  return typeof message.id === "string" && typeof message.name === "string";
71448
71448
  case "update-workspace":
71449
- return typeof message.workspaceID === "string" && typeof message.fields === "object" && message.fields !== null && (!("name" in message.fields) || typeof message.fields.name === "string");
71449
+ return typeof message.workspaceID === "string" && typeof message.fields === "object" && message.fields !== null && (!("name" in message.fields) || typeof message.fields.name === "string") && (!("layout" in message.fields) || Array.isArray(message.fields.layout) && message.fields.layout.every(
71450
+ (card) => typeof card === "object" && card !== null && "type" in card && card.type === "artifact" && "artifactID" in card && typeof card.artifactID === "string" && "cols" in card && typeof card.cols === "number" && "rows" in card && typeof card.rows === "number"
71451
+ ));
71450
71452
  case "delete-workspace":
71451
71453
  return typeof message.workspaceID === "string";
71452
71454
  default:
@@ -71604,8 +71606,83 @@ function ulid3(seedTime, prng) {
71604
71606
  var import_node_crypto2 = require("node:crypto");
71605
71607
  var import_node_fs = require("node:fs");
71606
71608
  var import_node_path2 = __toESM(require("node:path"), 1);
71609
+
71610
+ // src/types.ts
71611
+ var DEFAULT_LAYOUT_CARD_COLS = 2;
71612
+ var DEFAULT_LAYOUT_CARD_ROWS = 6;
71613
+ var LAYOUT_COLS_NARROW = 1;
71614
+ var LAYOUT_ROW_SHORT = 2;
71615
+ var LAYOUT_ROW_MID = 3;
71616
+ var LAYOUT_ROW_TALL = 4;
71617
+ var ALLOWED_COLS = [LAYOUT_COLS_NARROW, DEFAULT_LAYOUT_CARD_COLS];
71618
+ var ALLOWED_ROWS = [
71619
+ LAYOUT_ROW_SHORT,
71620
+ LAYOUT_ROW_MID,
71621
+ LAYOUT_ROW_TALL,
71622
+ DEFAULT_LAYOUT_CARD_ROWS
71623
+ ];
71624
+ function clampLayoutCols(cols) {
71625
+ const n = Math.round(cols);
71626
+ return ALLOWED_COLS.includes(n) ? n : DEFAULT_LAYOUT_CARD_COLS;
71627
+ }
71628
+ function clampLayoutRows(rows) {
71629
+ const n = Math.round(rows);
71630
+ return ALLOWED_ROWS.includes(n) ? n : DEFAULT_LAYOUT_CARD_ROWS;
71631
+ }
71632
+ function createLayoutCardRecord(artifactID) {
71633
+ return {
71634
+ type: "artifact",
71635
+ artifactID,
71636
+ cols: DEFAULT_LAYOUT_CARD_COLS,
71637
+ rows: DEFAULT_LAYOUT_CARD_ROWS
71638
+ };
71639
+ }
71640
+ function normalizeLayoutCardRecord(card) {
71641
+ return {
71642
+ type: "artifact",
71643
+ artifactID: card.artifactID,
71644
+ cols: clampLayoutCols(card.cols ?? DEFAULT_LAYOUT_CARD_COLS),
71645
+ rows: clampLayoutRows(card.rows ?? DEFAULT_LAYOUT_CARD_ROWS)
71646
+ };
71647
+ }
71648
+ function normalizeWorkspace(workspace) {
71649
+ const layout = Array.isArray(workspace.layout) ? workspace.layout.map((card) => normalizeLayoutCardRecord(card)) : [];
71650
+ return {
71651
+ id: workspace.id,
71652
+ name: workspace.name,
71653
+ layout
71654
+ };
71655
+ }
71656
+ function getWorkspaceArtifactIDs(workspace) {
71657
+ return workspace.layout.map((card) => card.artifactID);
71658
+ }
71659
+
71660
+ // src/server/workspace-store.ts
71607
71661
  var JSON_INDENT_SPACES = 2;
71608
71662
  var AUTH_TOKEN_BYTES = 32;
71663
+ var DEFAULT_COLS = 2;
71664
+ var DEFAULT_ROWS = 6;
71665
+ function migrateWorkspaceJson(raw) {
71666
+ if ("layout" in raw && Array.isArray(raw.layout) && raw.layout.length > 0) {
71667
+ return false;
71668
+ }
71669
+ if ("artifacts" in raw && Array.isArray(raw.artifacts)) {
71670
+ raw.layout = raw.artifacts;
71671
+ delete raw.artifacts;
71672
+ return true;
71673
+ }
71674
+ if ("artifactIDs" in raw && Array.isArray(raw.artifactIDs)) {
71675
+ raw.layout = raw.artifactIDs.map((id) => ({
71676
+ type: "artifact",
71677
+ artifactID: id,
71678
+ cols: DEFAULT_COLS,
71679
+ rows: DEFAULT_ROWS
71680
+ }));
71681
+ delete raw.artifactIDs;
71682
+ return true;
71683
+ }
71684
+ return false;
71685
+ }
71609
71686
  var WorkspaceStore = class extends Emitter {
71610
71687
  dataDir;
71611
71688
  workspaces = /* @__PURE__ */ new Map();
@@ -71632,10 +71709,12 @@ var WorkspaceStore = class extends Emitter {
71632
71709
  if (!workspace) {
71633
71710
  throw new Error(`Workspace not found: ${workspaceID}`);
71634
71711
  }
71635
- return workspace.artifactIDs.map((artifactID) => this.artifacts.get(artifactID)).filter((artifact) => artifact !== void 0);
71712
+ return getWorkspaceArtifactIDs(workspace).map((artifactID) => this.artifacts.get(artifactID)).filter((artifact) => artifact !== void 0);
71636
71713
  }
71637
71714
  findArtifactWorkspaces(artifactID) {
71638
- return [...this.workspaces.values()].filter((workspace) => workspace.artifactIDs.includes(artifactID));
71715
+ return [...this.workspaces.values()].filter(
71716
+ (workspace) => workspace.layout.some((card) => card.artifactID === artifactID)
71717
+ );
71639
71718
  }
71640
71719
  createArtifact(input) {
71641
71720
  if (!input.workspaceID) {
@@ -71649,7 +71728,7 @@ var WorkspaceStore = class extends Emitter {
71649
71728
  content: input.content
71650
71729
  };
71651
71730
  this.artifacts.set(artifact.id, artifact);
71652
- workspace.artifactIDs.push(artifact.id);
71731
+ workspace.layout.push(createLayoutCardRecord(artifact.id));
71653
71732
  this.persistArtifact(artifact);
71654
71733
  this.persistWorkspace(workspace);
71655
71734
  this.emit("mutation", {
@@ -71682,11 +71761,11 @@ var WorkspaceStore = class extends Emitter {
71682
71761
  throw new Error(`Artifact not found: ${input.artifactID}`);
71683
71762
  }
71684
71763
  const workspace = this.requireWorkspace(input.workspaceID);
71685
- if (!workspace.artifactIDs.includes(input.artifactID)) {
71764
+ if (!workspace.layout.some((card) => card.artifactID === input.artifactID)) {
71686
71765
  throw new Error(`Artifact not linked to workspace: ${input.artifactID}`);
71687
71766
  }
71688
71767
  this.artifacts.delete(input.artifactID);
71689
- workspace.artifactIDs = workspace.artifactIDs.filter((artifactID) => artifactID !== input.artifactID);
71768
+ workspace.layout = workspace.layout.filter((card) => card.artifactID !== input.artifactID);
71690
71769
  this.persistWorkspace(workspace);
71691
71770
  this.deleteArtifactFile(input.artifactID);
71692
71771
  this.emit("mutation", {
@@ -71699,7 +71778,7 @@ var WorkspaceStore = class extends Emitter {
71699
71778
  const workspace = {
71700
71779
  id: input.id ?? ulid3(),
71701
71780
  name: input.name,
71702
- artifactIDs: []
71781
+ layout: []
71703
71782
  };
71704
71783
  this.workspaces.set(workspace.id, workspace);
71705
71784
  this.persistWorkspace(workspace);
@@ -71711,18 +71790,26 @@ var WorkspaceStore = class extends Emitter {
71711
71790
  }
71712
71791
  updateWorkspace(input) {
71713
71792
  const workspace = this.requireWorkspace(input.workspaceID);
71714
- Object.assign(workspace, input.fields);
71793
+ const nextFields = {};
71794
+ if (typeof input.fields.name === "string") {
71795
+ workspace.name = input.fields.name;
71796
+ nextFields.name = input.fields.name;
71797
+ }
71798
+ if (Array.isArray(input.fields.layout)) {
71799
+ workspace.layout = input.fields.layout.map((card) => normalizeLayoutCardRecord(card));
71800
+ nextFields.layout = workspace.layout.map((card) => normalizeLayoutCardRecord(card));
71801
+ }
71715
71802
  this.persistWorkspace(workspace);
71716
71803
  this.emit("mutation", {
71717
71804
  type: "workspace-updated",
71718
71805
  workspaceID: input.workspaceID,
71719
- fields: input.fields
71806
+ fields: nextFields
71720
71807
  });
71721
71808
  }
71722
71809
  removeWorkspace(input) {
71723
71810
  const workspace = this.requireWorkspace(input.workspaceID);
71724
71811
  this.workspaces.delete(input.workspaceID);
71725
- for (const artifactID of workspace.artifactIDs) {
71812
+ for (const artifactID of getWorkspaceArtifactIDs(workspace)) {
71726
71813
  if (this.findArtifactWorkspaces(artifactID).length > 0) {
71727
71814
  continue;
71728
71815
  }
@@ -71744,9 +71831,15 @@ var WorkspaceStore = class extends Emitter {
71744
71831
  }
71745
71832
  load() {
71746
71833
  for (const file2 of this.readEntityFiles(this.workspacesDir)) {
71747
- const workspace = this.readJsonFile(import_node_path2.default.join(this.workspacesDir, file2));
71748
- if (!workspace) continue;
71834
+ const filePath = import_node_path2.default.join(this.workspacesDir, file2);
71835
+ const raw = this.readJsonFile(filePath);
71836
+ if (!raw || typeof raw !== "object") continue;
71837
+ const migrated = migrateWorkspaceJson(raw);
71838
+ const workspace = normalizeWorkspace(raw);
71749
71839
  this.workspaces.set(workspace.id, workspace);
71840
+ if (migrated) {
71841
+ this.persistWorkspace(workspace);
71842
+ }
71750
71843
  }
71751
71844
  for (const file2 of this.readEntityFiles(this.artifactsDir)) {
71752
71845
  const artifact = this.readJsonFile(import_node_path2.default.join(this.artifactsDir, file2));
@@ -71758,7 +71851,7 @@ var WorkspaceStore = class extends Emitter {
71758
71851
  const workspace = {
71759
71852
  id: ulid3(),
71760
71853
  name: "Default",
71761
- artifactIDs: []
71854
+ layout: []
71762
71855
  };
71763
71856
  this.workspaces.set(workspace.id, workspace);
71764
71857
  this.persistWorkspace(workspace);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telepath-computer/television",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "dist/electron.cjs",
6
6
  "bin": {
@@ -10,9 +10,10 @@
10
10
  "dist/**"
11
11
  ],
12
12
  "scripts": {
13
- "dev": "npm run dev:electron",
13
+ "dev": "npm run dev:server",
14
14
  "dev:electron": "scripts/dev-electron.sh",
15
15
  "dev:server": "scripts/dev-server.sh",
16
+ "demo:layout": "vite --config vite.demo-layout.config.ts --host 127.0.0.1",
16
17
  "build": "npm run build:renderer && npm run build:cli && npm run build:electron",
17
18
  "prepack": "npm run build",
18
19
  "start": "npm run build && env -u ELECTRON_RUN_AS_NODE electron .",