@ai-ecoverse/slicc-spectrum 1.13.0 → 1.14.0

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/dist/slicc-ui.js CHANGED
@@ -6597,6 +6597,14 @@ var surfaces = [
6597
6597
  icon: "sp-icon-settings",
6598
6598
  side: "center",
6599
6599
  open: []
6600
+ },
6601
+ {
6602
+ id: "updates",
6603
+ title: "Install / Update",
6604
+ tag: "slicc-updates",
6605
+ icon: "sp-icon-refresh",
6606
+ side: "center",
6607
+ open: []
6600
6608
  }
6601
6609
  ];
6602
6610
  var documents = {
@@ -6757,6 +6765,248 @@ function openSprinkle(dock, sprinkle, screen, place = null) {
6757
6765
  return id;
6758
6766
  }
6759
6767
 
6768
+ // src/app/updates.ts
6769
+ var states = {
6770
+ current: ["neutral", "Up to date"],
6771
+ checking: ["informative", "Checking"],
6772
+ downloading: ["informative", "Downloading"],
6773
+ linking: ["informative", "Linking"],
6774
+ installed: ["positive", "Installed"],
6775
+ available: ["notice", "Update available"],
6776
+ ready: ["notice", "Ready to apply"],
6777
+ failed: ["negative", "Failed"]
6778
+ };
6779
+ var actions = {
6780
+ "restart-agent": "Restart agent",
6781
+ reload: "Reload",
6782
+ "update-now": "Update now",
6783
+ retry: "Retry"
6784
+ };
6785
+ function updatesStatus(items) {
6786
+ const failed = items.filter((item2) => item2.state === "failed").length;
6787
+ if (failed) return `${failed} failed`;
6788
+ const running = items.filter(
6789
+ (item2) => ["checking", "downloading", "linking"].includes(item2.state)
6790
+ ).length;
6791
+ if (running) return `updating ${running}`;
6792
+ if (items.some((item2) => item2.state === "ready" || item2.state === "available")) {
6793
+ return "update ready";
6794
+ }
6795
+ return null;
6796
+ }
6797
+ var UpdateVisibility = class {
6798
+ #boot;
6799
+ #initial = true;
6800
+ #failed = /* @__PURE__ */ new Set();
6801
+ constructor(ready) {
6802
+ this.#boot = !ready;
6803
+ }
6804
+ change(port, layout = false) {
6805
+ const failed = new Set(
6806
+ port.list().filter((item2) => item2.state === "failed").map((item2) => item2.id)
6807
+ );
6808
+ const open = (this.#initial || layout) && this.#boot || [...failed].some((id) => !this.#failed.has(id));
6809
+ this.#initial = false;
6810
+ this.#failed = failed;
6811
+ if (this.#boot && port.ready() && failed.size === 0) {
6812
+ this.#boot = false;
6813
+ return "close";
6814
+ }
6815
+ return open ? "open" : null;
6816
+ }
6817
+ };
6818
+ var SliccUpdates = class extends ModelElement {
6819
+ static properties = {
6820
+ ...ModelElement.properties,
6821
+ pending: { state: true },
6822
+ failures: { state: true }
6823
+ };
6824
+ constructor() {
6825
+ super();
6826
+ this.pending = /* @__PURE__ */ new Set();
6827
+ this.failures = /* @__PURE__ */ new Map();
6828
+ }
6829
+ static styles = [
6830
+ shared,
6831
+ i`
6832
+ :host {
6833
+ display: block;
6834
+ height: 100%;
6835
+ overflow: auto;
6836
+ container-type: inline-size;
6837
+ background: var(--spectrum-background-layer-2-color);
6838
+ color: var(--spectrum-neutral-content-color-default);
6839
+ font-size: var(--spectrum-font-size-100);
6840
+ }
6841
+ .page {
6842
+ max-width: 800px;
6843
+ padding: 24px;
6844
+ margin: 0 auto;
6845
+ }
6846
+ h1 {
6847
+ font-size: var(--spectrum-font-size-400);
6848
+ margin: 0 0 8px;
6849
+ }
6850
+ .hint, .meta, summary {
6851
+ color: var(--spectrum-neutral-subdued-content-color-default);
6852
+ font-size: var(--spectrum-font-size-75);
6853
+ }
6854
+ .hint {
6855
+ margin: 0 0 24px;
6856
+ line-height: 1.5;
6857
+ }
6858
+ article {
6859
+ padding: 16px 0;
6860
+ border-top: 1px solid var(--spectrum-gray-200);
6861
+ }
6862
+ .head, .meta, .actions {
6863
+ display: flex;
6864
+ align-items: center;
6865
+ flex-wrap: wrap;
6866
+ gap: 8px 12px;
6867
+ }
6868
+ .head {
6869
+ justify-content: space-between;
6870
+ margin-bottom: 8px;
6871
+ }
6872
+ .meta {
6873
+ justify-content: space-between;
6874
+ line-height: 1.5;
6875
+ }
6876
+ .version {
6877
+ font-family: var(--spectrum-code-font-family);
6878
+ }
6879
+ progress {
6880
+ display: block;
6881
+ width: 100%;
6882
+ height: 4px;
6883
+ margin: 12px 0;
6884
+ accent-color: var(--spectrum-accent-visual-color);
6885
+ }
6886
+ .actions {
6887
+ margin-top: 12px;
6888
+ }
6889
+ .error {
6890
+ margin: 12px 0 0;
6891
+ padding: 12px;
6892
+ border-left: 3px solid var(--spectrum-negative-visual-color);
6893
+ background: var(--spectrum-background-layer-1-color);
6894
+ color: var(--spectrum-negative-content-color-default);
6895
+ overflow-wrap: anywhere;
6896
+ line-height: 1.5;
6897
+ }
6898
+ details {
6899
+ margin-top: 12px;
6900
+ }
6901
+ summary {
6902
+ cursor: pointer;
6903
+ width: fit-content;
6904
+ }
6905
+ summary:focus-visible {
6906
+ outline: 2px solid var(--spectrum-focus-indicator-color);
6907
+ outline-offset: 3px;
6908
+ }
6909
+ pre {
6910
+ padding: 12px;
6911
+ max-height: 180px;
6912
+ overflow: auto;
6913
+ background: var(--spectrum-background-layer-1-color);
6914
+ border: 1px solid var(--spectrum-gray-200);
6915
+ border-radius: 4px;
6916
+ font-family: var(--spectrum-code-font-family);
6917
+ font-size: var(--spectrum-font-size-75);
6918
+ line-height: 1.5;
6919
+ white-space: pre-wrap;
6920
+ overflow-wrap: anywhere;
6921
+ }
6922
+ @container (max-width: 480px) {
6923
+ .page {
6924
+ padding: 16px;
6925
+ }
6926
+ .head, .meta {
6927
+ align-items: flex-start;
6928
+ flex-direction: column;
6929
+ }
6930
+ }
6931
+ `
6932
+ ];
6933
+ subscribe(model) {
6934
+ this.pending = /* @__PURE__ */ new Set();
6935
+ this.failures = /* @__PURE__ */ new Map();
6936
+ const port = model.updates;
6937
+ if (!port) return [];
6938
+ let previous = new Map(port.list().map(({ id, state, error }) => [id, { state, error }]));
6939
+ return [
6940
+ port.on("items", (items) => {
6941
+ const next = new Map(items.map(({ id, state, error }) => [id, { state, error }]));
6942
+ this.failures = new Map(
6943
+ [...this.failures].filter(([id]) => {
6944
+ const before = previous.get(id);
6945
+ const after = next.get(id);
6946
+ return before && after && before.state === after.state && before.error === after.error;
6947
+ })
6948
+ );
6949
+ previous = next;
6950
+ this.requestUpdate();
6951
+ })
6952
+ ];
6953
+ }
6954
+ focus() {
6955
+ this.focusOn("sp-button, summary");
6956
+ }
6957
+ async act(id, action) {
6958
+ const port = this.model?.updates;
6959
+ if (!port || this.pending.has(id)) return;
6960
+ this.pending = /* @__PURE__ */ new Set([...this.pending, id]);
6961
+ this.failures = new Map([...this.failures].filter(([key2]) => key2 !== id));
6962
+ try {
6963
+ await port.act(id, action);
6964
+ } catch (error) {
6965
+ if (this.model?.updates === port) {
6966
+ this.failures = new Map([
6967
+ ...this.failures,
6968
+ [id, error instanceof Error ? error.message : String(error)]
6969
+ ]);
6970
+ }
6971
+ } finally {
6972
+ if (this.model?.updates === port) {
6973
+ this.pending = new Set([...this.pending].filter((key2) => key2 !== id));
6974
+ }
6975
+ }
6976
+ }
6977
+ #item(item2) {
6978
+ const [variant, label] = states[item2.state];
6979
+ const progress = item2.progress;
6980
+ const error = this.failures.get(item2.id) ?? item2.error;
6981
+ return b`<article data-id=${item2.id} data-state=${item2.state} aria-label=${item2.label}>
6982
+ <div class="head">
6983
+ <strong>${item2.label}</strong>
6984
+ <sp-badge size="s" variant=${variant}>${label}${progress ? ` ${progress.done}/${progress.total}` : ""}</sp-badge>
6985
+ </div>
6986
+ <div class="meta">
6987
+ <span class="version">${item2.from ?? "Not installed"}${item2.to && item2.to !== item2.from ? b` → ${item2.to}` : A}</span>
6988
+ ${item2.checkedAt === null ? b`<span>Not checked yet</span>` : b`<span>Checked <time datetime=${new Date(item2.checkedAt).toISOString()}>${new Date(item2.checkedAt).toLocaleString("en-GB", { dateStyle: "medium", timeStyle: "short" })}</time></span>`}
6989
+ </div>
6990
+ ${progress ? b`<progress aria-label=${`${item2.label}: ${progress.phase === "download" ? "downloading" : "linking"}`} value=${progress.done} max=${progress.total}></progress>` : A}
6991
+ ${error ? b`<div class="error" role="alert">${error}</div>` : A}
6992
+ ${item2.actions.length ? b`<div class="actions">${item2.actions.map((action) => b`<sp-button size="s" variant="secondary" treatment="outline" ?disabled=${this.pending.has(item2.id)} ?pending=${this.pending.has(item2.id)} data-action=${action} @click=${() => void this.act(item2.id, action)}>${actions[action]}</sp-button>`)}</div>` : A}
6993
+ ${item2.log ? b`<details><summary>Install log</summary><pre>${item2.log}</pre></details>` : A}
6994
+ </article>`;
6995
+ }
6996
+ render() {
6997
+ const port = this.model?.updates;
6998
+ return b`<div class="page">
6999
+ <h1>Install / Update</h1>
7000
+ <p class="hint">${!port ? "Update information is not connected." : port.ready() ? "Your agent is ready. Background updates appear here; apply them when it suits you." : "Preparing your agent. You can keep working; this panel closes when the agent is ready."}</p>
7001
+ ${c(
7002
+ port?.list() ?? [],
7003
+ (item2) => item2.id,
7004
+ (item2) => this.#item(item2)
7005
+ )}
7006
+ </div>`;
7007
+ }
7008
+ };
7009
+
6760
7010
  // src/app/app.ts
6761
7011
  function railIcon(icon) {
6762
7012
  if (icon.startsWith("sp-icon-")) {
@@ -6777,6 +7027,8 @@ var SliccApp = class extends ThemedElement {
6777
7027
  #resize = null;
6778
7028
  #started = null;
6779
7029
  #saving = false;
7030
+ #updatesPort;
7031
+ #updatesVisibility = null;
6780
7032
  constructor() {
6781
7033
  super();
6782
7034
  this.screen = screenClass(globalThis.innerWidth ?? 1280);
@@ -6797,6 +7049,7 @@ var SliccApp = class extends ThemedElement {
6797
7049
  }
6798
7050
  .shell {
6799
7051
  display: grid;
7052
+ grid-template-columns: minmax(0, 1fr);
6800
7053
  grid-template-rows: 40px minmax(0, 1fr) auto 24px;
6801
7054
  height: 100%;
6802
7055
  }
@@ -6834,11 +7087,12 @@ var SliccApp = class extends ThemedElement {
6834
7087
  }
6835
7088
  .rail.bottom {
6836
7089
  flex-direction: row;
6837
- justify-content: center;
7090
+ justify-content: safe center;
6838
7091
  width: auto;
6839
7092
  height: 44px;
6840
7093
  padding: 0 8px;
6841
7094
  border-top: 1px solid var(--spectrum-gray-200);
7095
+ overflow-x: auto;
6842
7096
  }
6843
7097
  .rail:empty {
6844
7098
  display: none;
@@ -6846,6 +7100,14 @@ var SliccApp = class extends ThemedElement {
6846
7100
  :host([screen='phone']) footer .hints {
6847
7101
  display: none;
6848
7102
  }
7103
+ :host([screen='phone']) header sp-picker {
7104
+ min-width: 0;
7105
+ width: 0;
7106
+ flex: 1;
7107
+ }
7108
+ :host([screen='phone']) header .spacer {
7109
+ display: none;
7110
+ }
6849
7111
  header {
6850
7112
  display: flex;
6851
7113
  align-items: center;
@@ -6941,7 +7203,8 @@ var SliccApp = class extends ThemedElement {
6941
7203
  }),
6942
7204
  model.files.on("changes", update),
6943
7205
  model.sprinkles.on("sprinkles", update),
6944
- this.dips.on("change", update)
7206
+ this.dips.on("change", update),
7207
+ ...model.updates ? [model.updates.on("items", update)] : []
6945
7208
  ];
6946
7209
  }
6947
7210
  connectedCallback() {
@@ -6978,8 +7241,28 @@ var SliccApp = class extends ThemedElement {
6978
7241
  }
6979
7242
  updated(changed) {
6980
7243
  if (!this.model) return;
6981
- if (this.#started !== this.screen) this.#start(this.model);
7244
+ const layout = this.#started !== this.screen;
7245
+ if (layout) this.#start(this.model);
6982
7246
  else if (changed.has("model")) this.#rebind(this.model);
7247
+ this.#updates(layout);
7248
+ }
7249
+ #updates(layout) {
7250
+ const port = this.model?.updates;
7251
+ if (port !== this.#updatesPort) {
7252
+ this.#updatesPort = port;
7253
+ this.#updatesVisibility = port ? new UpdateVisibility(port.ready()) : null;
7254
+ }
7255
+ if (!port || !this.#offers("updates")) return;
7256
+ const change = this.#updatesVisibility?.change(port, layout);
7257
+ if (change === "open") {
7258
+ if (this.dock.has("updates")) this.dock.reveal("updates");
7259
+ else {
7260
+ this.show("updates");
7261
+ if (!port.ready()) this.dock.api.getPanel("updates")?.api.updateParameters({ boot: true });
7262
+ }
7263
+ } else if (this.dock.api.getPanel("updates")?.params?.boot && port.ready() && !port.list().some((item2) => item2.state === "failed")) {
7264
+ this.dock.close("updates");
7265
+ }
6983
7266
  }
6984
7267
  #factories(model) {
6985
7268
  return {
@@ -7072,6 +7355,7 @@ var SliccApp = class extends ThemedElement {
7072
7355
  const item2 = surface(id, this.surfaces);
7073
7356
  if (!item2) return;
7074
7357
  openSurface(this.dock, item2, this.screen);
7358
+ if (id === "updates") this.dock.api.getPanel(id)?.api.updateParameters({ boot: false });
7075
7359
  this.dock.focusPanel(id);
7076
7360
  }
7077
7361
  #sprinkle(id) {
@@ -7150,7 +7434,7 @@ var SliccApp = class extends ThemedElement {
7150
7434
  }
7151
7435
  #menuItem(surface2, index) {
7152
7436
  return b`<sp-menu-item value=${surface2.id}>
7153
- ${surface2.title}<kbd slot="value">Alt+${index + 1}</kbd>
7437
+ ${surface2.title}${index < 9 ? b`<kbd slot="value">Alt+${index + 1}</kbd>` : A}
7154
7438
  </sp-menu-item>`;
7155
7439
  }
7156
7440
  #offers(id) {
@@ -7174,7 +7458,9 @@ var SliccApp = class extends ThemedElement {
7174
7458
  return b`<button class="link" @click=${() => this.show("changes")}>${changes} ${changes === 1 ? "change" : "changes"}</button>`;
7175
7459
  }
7176
7460
  #status() {
7461
+ const updates = this.#offers("updates") ? updatesStatus(this.model?.updates?.list() ?? []) : null;
7177
7462
  return b`<footer role="status" aria-label="Status">
7463
+ ${updates ? b`<button class="link" aria-label=${`Install / Update: ${updates}`} data-updates @click=${() => this.show("updates")}>${updates}</button>` : A}
7178
7464
  ${this.#agentStatus()}
7179
7465
  ${this.#changes()}
7180
7466
  <slot name="status"></slot>
@@ -7196,7 +7482,7 @@ var SliccApp = class extends ThemedElement {
7196
7482
  )}
7197
7483
  </sp-picker>` : A}
7198
7484
  <span class="spacer"></span>
7199
- ${this.#offers("chat") ? b`<slicc-tray .model=${this.model}></slicc-tray>` : A}
7485
+ ${this.#offers("chat") ? b`<slicc-tray .model=${this.model} ?compact=${this.screen === "phone"}></slicc-tray>` : A}
7200
7486
  <sp-action-menu size="s" quiet label="View" @change=${this.#view}>
7201
7487
  <sp-icon-view-grid slot="icon"></sp-icon-view-grid>
7202
7488
  <span slot="label">View</span>
@@ -8075,18 +8361,18 @@ function lick(message, handlers) {
8075
8361
  ${message.count && message.count > 1 ? b`<span class="times" title="Coalesced events">×${message.count}</span>` : A}
8076
8362
  ${outcome ? b`<span class="tag">${outcome}</span>` : A}
8077
8363
  <span class="when">${time(message.createdAt)}</span>`;
8078
- const actions = decide ? b`<div class="decide">
8364
+ const actions2 = decide ? b`<div class="decide">
8079
8365
  <sp-button size="s" variant="accent" @click=${() => handlers.resolve(message.id, "confirmed")}>Allow</sp-button>
8080
8366
  <sp-button size="s" variant="secondary" treatment="outline" @click=${() => handlers.resolve(message.id, "dismissed")}>Deny</sp-button>
8081
8367
  </div>` : A;
8082
8368
  if (!message.body) {
8083
- return b`<div class="lick" data-id=${message.id} data-channel=${message.channel}><div class="lick-head">${head2}</div>${actions}</div>`;
8369
+ return b`<div class="lick" data-id=${message.id} data-channel=${message.channel}><div class="lick-head">${head2}</div>${actions2}</div>`;
8084
8370
  }
8085
8371
  const details = b`<details class="lick" data-id=${message.id} data-channel=${message.channel}>
8086
8372
  <summary class="lick-head"><span class="chevron" aria-hidden="true">▸</span>${head2}</summary>
8087
8373
  <pre class="output">${message.body}</pre>
8088
8374
  </details>`;
8089
- return decide ? b`<div class="pending">${details}${actions}</div>` : details;
8375
+ return decide ? b`<div class="pending">${details}${actions2}</div>` : details;
8090
8376
  }
8091
8377
  var messageCss = i`
8092
8378
  .message {
@@ -10889,6 +11175,9 @@ var SliccTray = class extends ModelElement {
10889
11175
  .chip:hover {
10890
11176
  background: var(--spectrum-gray-100);
10891
11177
  }
11178
+ :host([compact]) .chip > span:not(.dot) {
11179
+ display: none;
11180
+ }
10892
11181
  .chip:focus-visible {
10893
11182
  outline: 2px solid var(--spectrum-focus-indicator-color);
10894
11183
  }
@@ -62248,6 +62537,7 @@ var elements = {
62248
62537
  "slicc-sprinkle": SliccSprinkle,
62249
62538
  "slicc-lucide": SliccLucide,
62250
62539
  "slicc-tray": SliccTray,
62540
+ "slicc-updates": SliccUpdates,
62251
62541
  "slicc-app": SliccApp
62252
62542
  };
62253
62543
  function define2() {
@@ -62275,6 +62565,7 @@ export {
62275
62565
  SliccFileTree,
62276
62566
  SliccFileView,
62277
62567
  SliccFiles,
62568
+ SliccUpdates,
62278
62569
  define2 as define,
62279
62570
  grammarBase,
62280
62571
  resolveColor,