selfstore 1.8.15 → 1.8.17

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/flows.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { b as ConnectKind, S as StoreLike, C as ConnectTargets } from './connect-DaZFPJdt.js';
2
- export { c as ConnectFlow, a as ConnectFlowOptions, d as ConnectFlowOutcome, e as ConnectResolution, f as ConnectSnapshot, g as ConnectStep, h as Connector, F as FlowHost, R as ResumeOffer, i as connectFlow } from './connect-DaZFPJdt.js';
3
- import { S as StoreError, k as ReplicaState, B as BackupTarget } from './store-CiLoRJmy.js';
1
+ import { b as ConnectKind, S as StoreLike, C as ConnectTargets } from './connect-BaWYvB1w.js';
2
+ export { c as ConnectFlow, a as ConnectFlowOptions, d as ConnectFlowOutcome, e as ConnectResolution, f as ConnectSnapshot, g as ConnectStep, h as Connector, F as FlowHost, R as ResumeOffer, i as connectFlow } from './connect-BaWYvB1w.js';
3
+ import { S as StoreError, k as ReplicaState, B as BackupTarget } from './store-CCe8b5Eg.js';
4
4
  import { F as FlowStore } from './machine-ALMkT3mk.js';
5
5
  export { w as withDeadline } from './machine-ALMkT3mk.js';
6
- import { W as WebdavConfig, S as S3Config } from './s3-DgyTwMRc.js';
6
+ import { W as WebdavConfig, S as S3Config } from './s3-CCE6lc_U.js';
7
7
  import './cache-ilsK3Tki.js';
8
- import './drive-B2fm71qD.js';
8
+ import './drive-BUPsPaIu.js';
9
9
  import './sync.js';
10
10
 
11
11
  /** The one replica this flow manages, as its id on the engine. */
@@ -1,4 +1,4 @@
1
- import { B as BackupTarget, P as PeerSource, L as LocalStore } from './store-CiLoRJmy.js';
1
+ import { B as BackupTarget, P as PeerSource, L as LocalStore } from './store-CCe8b5Eg.js';
2
2
  import { K as KV } from './cache-ilsK3Tki.js';
3
3
  import './sync.js';
4
4
 
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { o as Snapshot, E as EncodeOptions, H as Header, f as PasswordPolicy, B as BackupTarget, r as StatusDescriptor, S as StoreError, c as LocalStoreState, L as LocalStore } from './store-CiLoRJmy.js';
2
- export { d as Mode, e as PasswordCheck, g as PasswordRequirement, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, p as SnapshotFile, q as StatusAction, t as StorageState, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-CiLoRJmy.js';
1
+ import { o as Snapshot, E as EncodeOptions, H as Header, f as PasswordPolicy, B as BackupTarget, r as StatusDescriptor, S as StoreError, c as LocalStoreState, L as LocalStore } from './store-CCe8b5Eg.js';
2
+ export { d as Mode, e as PasswordCheck, g as PasswordRequirement, l as SelfstoreError, m as SelfstoreErrorCode, n as Severity, p as SnapshotFile, q as StatusAction, t as StorageState, T as TargetKind, x as checkPasswordPolicy, C as errorLabelKey, G as isSelfstoreError } from './store-CCe8b5Eg.js';
3
3
  import { L as LocalCache, K as KV } from './cache-ilsK3Tki.js';
4
4
  import { SyncConfig } from './sync.js';
5
- import { R as ResumeOffer, C as ConnectTargets } from './connect-DaZFPJdt.js';
6
- import { f as BackupsManager } from './manager-BqzTVaep.js';
7
- import { a as DriveAuth } from './drive-B2fm71qD.js';
8
- import { W as WebdavConfig, S as S3Config } from './s3-DgyTwMRc.js';
9
- export { f as changePassword, g as gisDriveAuth, p as pickFromDisk, s as saveToDisk } from './local-CldSa9EY.js';
5
+ import { R as ResumeOffer, C as ConnectTargets } from './connect-BaWYvB1w.js';
6
+ import { f as BackupsManager } from './manager-CaMd6T1T.js';
7
+ import { a as DriveAuth } from './drive-BUPsPaIu.js';
8
+ import { W as WebdavConfig, S as S3Config } from './s3-CCE6lc_U.js';
9
+ export { f as changePassword, g as gisDriveAuth, p as pickFromDisk, s as saveToDisk } from './local-Dt1hYNG4.js';
10
10
  import './machine-ALMkT3mk.js';
11
11
 
12
12
  declare const BACKUP_EXTENSION = ".zip";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-TXAMWBG5.js';
2
- export { gisDriveAuth } from './chunk-TXAMWBG5.js';
1
+ import { indexedDbCache, memoryCache, gisDriveAuth, isLockableCache, createLocalStore } from './chunk-3BPGPSPX.js';
2
+ export { gisDriveAuth } from './chunk-3BPGPSPX.js';
3
3
  import './chunk-SKWHKM25.js';
4
4
  import { saveToDisk } from './chunk-DCEXWSM6.js';
5
5
  export { pickFromDisk, saveToDisk } from './chunk-DCEXWSM6.js';
@@ -1,5 +1,5 @@
1
- import { o as Snapshot, E as EncodeOptions, H as Header } from './store-CiLoRJmy.js';
2
- import { a as DriveAuth } from './drive-B2fm71qD.js';
1
+ import { o as Snapshot, E as EncodeOptions, H as Header } from './store-CCe8b5Eg.js';
2
+ import { a as DriveAuth } from './drive-BUPsPaIu.js';
3
3
 
4
4
  /** Serialize a snapshot to a portable backup blob (compressed; encrypted if a password is given). */
5
5
  declare function exportSnapshot(snapshot: Snapshot, opts: EncodeOptions): Promise<Blob>;
@@ -1,5 +1,5 @@
1
1
  import { K as KV } from './cache-ilsK3Tki.js';
2
- import { B as BackupTarget, L as LocalStore } from './store-CiLoRJmy.js';
2
+ import { B as BackupTarget, L as LocalStore } from './store-CCe8b5Eg.js';
3
3
  import { F as FlowStore } from './machine-ALMkT3mk.js';
4
4
 
5
5
  /** What the destination reports about a listed file (one cheap metadata call). */
@@ -1,4 +1,4 @@
1
- import { B as BackupTarget, P as PeerSource } from './store-CiLoRJmy.js';
1
+ import { B as BackupTarget, P as PeerSource } from './store-CCe8b5Eg.js';
2
2
  import { K as KV } from './cache-ilsK3Tki.js';
3
3
 
4
4
  /**
@@ -359,6 +359,24 @@ interface LocalStoreState {
359
359
  locked: boolean;
360
360
  saving: boolean;
361
361
  lastSavedAt: number | null;
362
+ /**
363
+ * When a PORTABLE COPY was last written (an export the user asked for), or
364
+ * null if never. Distinct from `lastSavedAt`, which is the durable
365
+ * destination: a copy is a snapshot that leaves, a destination is kept up to
366
+ * date.
367
+ */
368
+ lastCopyAt: number | null;
369
+ /**
370
+ * Entries have been recorded since that copy was written.
371
+ *
372
+ * The store knew this only in its download-only mode, where the copy IS the
373
+ * save. Everywhere else nobody followed it, so an app that let its user
374
+ * export could say nothing about a copy going stale - and one of them found
375
+ * out the hard way that a copy written in the morning was still announced as
376
+ * up to date in the evening. False while no copy was ever written: there is
377
+ * nothing to be stale.
378
+ */
379
+ copyIsStale: boolean;
362
380
  status: StatusDescriptor;
363
381
  /** The last durable-target problem (upload/refresh), or null. */
364
382
  lastError: StoreError | null;
package/dist/widgets.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { S as StoreLike, F as FlowHost, C as ConnectTargets, a as ConnectFlowOptions, b as ConnectKind, c as ConnectFlow } from './connect-DaZFPJdt.js';
1
+ import { S as StoreLike, F as FlowHost, C as ConnectTargets, a as ConnectFlowOptions, b as ConnectKind, c as ConnectFlow } from './connect-BaWYvB1w.js';
2
2
  import { ShareEngine, ShareLevel, ShareFlow, JoinEngine, JoinFlow, ReplicaFlow } from './flows.js';
3
- import { f as BackupsManager } from './manager-BqzTVaep.js';
4
- import './store-CiLoRJmy.js';
3
+ import { f as BackupsManager } from './manager-CaMd6T1T.js';
4
+ import './store-CCe8b5Eg.js';
5
5
  import './cache-ilsK3Tki.js';
6
6
  import './sync.js';
7
- import './drive-B2fm71qD.js';
8
- import './s3-DgyTwMRc.js';
7
+ import './drive-BUPsPaIu.js';
8
+ import './s3-CCE6lc_U.js';
9
9
  import './machine-ALMkT3mk.js';
10
10
 
11
11
  /** Flat key -> string map; widgets merge a partial override over EN defaults. */
@@ -356,9 +356,13 @@ declare class SelfstoreStatusElement extends FlowWidget {
356
356
  static get observedAttributes(): string[];
357
357
  protected defaults(): WidgetLabels;
358
358
  protected packs(): Record<string, WidgetLabels>;
359
- /** 'row' (dot + text + action) or 'dot' (the dot alone). Also the attribute. */
360
- get variant(): 'row' | 'dot';
361
- set variant(v: 'row' | 'dot' | null);
359
+ /**
360
+ * 'row' (a tinted box: dot, text, action button - for a settings page),
361
+ * 'line' (one line and a link - for a menu, where a box eats the surface),
362
+ * or 'dot' (the dot alone, for a header corner). Also the attribute.
363
+ */
364
+ get variant(): 'row' | 'line' | 'dot';
365
+ set variant(v: 'row' | 'line' | 'dot' | null);
362
366
  attributeChangedCallback(name: string, _old: string | null, value: string | null): void;
363
367
  connectedCallback(): void;
364
368
  protected view(into: HTMLElement): void;
@@ -742,6 +746,7 @@ declare class SelfstoreStorageElement extends FlowWidget {
742
746
 
743
747
  declare class SelfstoreAccountElement extends FlowWidget {
744
748
  #private;
749
+ static get observedAttributes(): string[];
745
750
  protected defaults(): WidgetLabels;
746
751
  protected packs(): Record<string, WidgetLabels>;
747
752
  constructor();
@@ -751,10 +756,23 @@ declare class SelfstoreAccountElement extends FlowWidget {
751
756
  * the user picks one. Returns false (or throws) to stop. */
752
757
  get confirmAction(): ((a: DestinationAction) => boolean | Promise<boolean>) | null;
753
758
  set confirmAction(fn: ((a: DestinationAction) => boolean | Promise<boolean>) | null);
759
+ /**
760
+ * What the trigger says: the DESTINATION it goes to (default), or the STATE
761
+ * it is in.
762
+ *
763
+ * Naming the place is right when there is a choice - it makes local-first
764
+ * visible without opening anything. It says nothing at all when an app
765
+ * offers exactly one destination: "a file" where a file is the only thing it
766
+ * could ever be. Such an app states a condition instead - saved, unsaved,
767
+ * locked - which is the part that changes.
768
+ */
769
+ get trigger(): 'destination' | 'status';
770
+ set trigger(v: 'destination' | 'status' | null);
754
771
  /** Whether the menu is on screen. Settable, so a host can close it from its
755
772
  * own router after the settings item navigated away. */
756
773
  get open(): boolean;
757
774
  set open(v: boolean);
775
+ attributeChangedCallback(name: string): void;
758
776
  connectedCallback(): void;
759
777
  disconnectedCallback(): void;
760
778
  /** While the menu is open: a clock for the relative time, and the two ways
@@ -767,13 +785,19 @@ declare class SelfstoreAccountElement extends FlowWidget {
767
785
  * shipped name: its own label is a better answer than the raw key. */
768
786
  private named;
769
787
  /**
770
- * The one line under the account. A plain "saved" is the only state this
771
- * element words itself, because it is the only one with nothing to do about
772
- * it - and the only one where WHEN is the whole information. Everything else
773
- * (reconnect, unlock, changes to download) belongs to <selfstore-status>,
774
- * which owns both the wording and the button that resolves it.
788
+ * The one line this element words itself: a plain "saved", the only state
789
+ * with nothing to do about it and the only one where WHEN is the whole
790
+ * information. Null in every other state.
791
+ */
792
+ private saved;
793
+ /**
794
+ * Anything that is NOT a plain "saved" - reconnect, unlock, changes to
795
+ * export - belongs to <selfstore-status>, which owns both the wording and
796
+ * the gesture. It goes under the card and across the whole menu, never
797
+ * inside the card's text column: that column is sized for an address, and a
798
+ * sentence wrapping in 167 px turned a three-word notice into 247 px of box.
775
799
  */
776
- private line;
800
+ private attention;
777
801
  /** Ask for the settings. The host owns the route, so this only says that it
778
802
  * was asked - and closes, because a menu that stays open behind a page
779
803
  * transition reads as a failed click. */
@@ -781,6 +805,13 @@ declare class SelfstoreAccountElement extends FlowWidget {
781
805
  /** Change backup: detach, and let the first-run screen take it from there.
782
806
  * The data stays; only the destination is let go. */
783
807
  private change;
808
+ /**
809
+ * What the trigger reads. The destination by default; the state when an app
810
+ * has only one destination to offer, where naming it says nothing that ever
811
+ * changes. The state comes from <selfstore-status>'s own words, so the two
812
+ * surfaces cannot drift.
813
+ */
814
+ private said;
784
815
  private card;
785
816
  protected view(into: HTMLElement): void;
786
817
  /**
package/dist/widgets.js CHANGED
@@ -2071,14 +2071,14 @@ var EN4 = {
2071
2071
  // and a backup is written only when the user asks for one. It is a mode the
2072
2072
  // STORE itself falls back to, so the library owes it a name - without one, a
2073
2073
  // panel showed the raw key to the user.
2074
- "destination.kind.file-manual": "A file you download"
2074
+ "destination.kind.file-manual": "A file"
2075
2075
  };
2076
2076
  var FR4 = {
2077
2077
  "destination.kind.file": "Un fichier sur cet appareil",
2078
2078
  "destination.kind.webdav": "Votre serveur",
2079
2079
  "destination.kind.s3": "Votre bucket",
2080
2080
  "destination.kind.device": "Cet appareil seulement",
2081
- "destination.kind.file-manual": "Un fichier, par t\xE9l\xE9chargement"
2081
+ "destination.kind.file-manual": "Un fichier"
2082
2082
  };
2083
2083
 
2084
2084
  // src/widgets/status.ts
@@ -2125,12 +2125,16 @@ var SelfstoreStatusElement = class extends FlowWidget {
2125
2125
  packs() {
2126
2126
  return { fr: FR5 };
2127
2127
  }
2128
- /** 'row' (dot + text + action) or 'dot' (the dot alone). Also the attribute. */
2128
+ /**
2129
+ * 'row' (a tinted box: dot, text, action button - for a settings page),
2130
+ * 'line' (one line and a link - for a menu, where a box eats the surface),
2131
+ * or 'dot' (the dot alone, for a header corner). Also the attribute.
2132
+ */
2129
2133
  get variant() {
2130
2134
  return this.#variant;
2131
2135
  }
2132
2136
  set variant(v) {
2133
- this.#variant = v === "dot" ? "dot" : "row";
2137
+ this.#variant = v === "dot" || v === "line" ? v : "row";
2134
2138
  this.rerender();
2135
2139
  }
2136
2140
  attributeChangedCallback(name, _old, value) {
@@ -2138,7 +2142,8 @@ var SelfstoreStatusElement = class extends FlowWidget {
2138
2142
  }
2139
2143
  connectedCallback() {
2140
2144
  super.connectedCallback();
2141
- if (this.hasAttribute("variant")) this.variant = this.getAttribute("variant");
2145
+ if (this.hasAttribute("variant"))
2146
+ this.variant = this.getAttribute("variant");
2142
2147
  this.follow(this.hostOf());
2143
2148
  }
2144
2149
  view(into) {
@@ -2150,12 +2155,20 @@ var SelfstoreStatusElement = class extends FlowWidget {
2150
2155
  const copie = this.t(jumelle);
2151
2156
  return copie === jumelle ? this.t(cle) : copie;
2152
2157
  };
2158
+ const selonLeType = (cle) => {
2159
+ const jumelle = `${cle}.${targetKind}`;
2160
+ const copie = this.t(jumelle, { label: label ?? "" });
2161
+ return copie === jumelle ? null : copie;
2162
+ };
2153
2163
  const { lastSavedAt } = host.engine.state;
2154
- const text = label ? this.t(status.labelKey, { label }) : sansLieu(status.labelKey);
2164
+ const text = selonLeType(status.labelKey) ?? (label ? this.t(status.labelKey, { label }) : sansLieu(status.labelKey));
2155
2165
  const dot = h("span", {
2156
2166
  part: `status-dot sev-${status.severity}`,
2157
2167
  "aria-hidden": "true"
2158
2168
  });
2169
+ const GLYPH = { ok: "\u2713", warn: "!", danger: "!", info: "" };
2170
+ const glyph = GLYPH[status.severity];
2171
+ const sign = glyph ? h("span", { part: `status-glyph sev-${status.severity}`, "aria-hidden": "true" }, glyph) : dot;
2159
2172
  if (this.#variant === "dot") {
2160
2173
  into.append(
2161
2174
  h(
@@ -2171,14 +2184,33 @@ var SelfstoreStatusElement = class extends FlowWidget {
2171
2184
  );
2172
2185
  return;
2173
2186
  }
2187
+ if (this.#variant === "line") {
2188
+ put(
2189
+ into,
2190
+ h(
2191
+ "div",
2192
+ { part: "row status-line" },
2193
+ sign,
2194
+ h("span", { part: "status-line-text" }, text),
2195
+ status.actionable && status.action ? h(
2196
+ "button",
2197
+ {
2198
+ part: "link status-action",
2199
+ type: "button",
2200
+ onclick: () => this.emit("selfstore-status-action", { action: status.action })
2201
+ },
2202
+ this.t(`status.action.${status.action}`)
2203
+ ) : null
2204
+ )
2205
+ );
2206
+ return;
2207
+ }
2174
2208
  const TINT = {
2175
2209
  ok: "status-ok",
2176
2210
  warn: "status-warn",
2177
2211
  danger: "status-error",
2178
2212
  info: ""
2179
2213
  };
2180
- const GLYPH = { ok: "\u2713", warn: "!", danger: "!", info: "" };
2181
- const glyph = GLYPH[status.severity];
2182
2214
  const icon = this.icons[targetKind];
2183
2215
  const where = this.t(`status.where.${targetKind}`);
2184
2216
  const kindName = this.t(`destination.kind.${targetKind}`);
@@ -2187,15 +2219,15 @@ var SelfstoreStatusElement = class extends FlowWidget {
2187
2219
  const place = where !== `status.where.${targetKind}` ? where : own ?? named ?? label ?? "";
2188
2220
  const when = lastSavedAt ? this.since(lastSavedAt, this.t("status.justNow")) : "";
2189
2221
  const sub = [place, status.state === "saved" ? when : ""].filter(Boolean).join(", ");
2190
- const title = sub ? sansLieu(status.labelKey) : text;
2222
+ const title = selonLeType(status.labelKey) ?? (sub ? sansLieu(status.labelKey) : text);
2191
2223
  put(
2192
2224
  into,
2193
2225
  h(
2194
2226
  "div",
2195
2227
  { part: `status row status-row ${TINT[status.severity] ?? ""}` },
2196
- // The severity colors the glyph, not the box: tinting the whole line
2228
+ // The severity colors the sign, not the box: tinting the whole line
2197
2229
  // turns a title into a warning about itself.
2198
- glyph ? h("span", { part: `status-glyph sev-${status.severity}`, "aria-hidden": "true" }, glyph) : dot,
2230
+ sign,
2199
2231
  icon ? h("img", { part: "icon", src: icon, alt: "" }) : null,
2200
2232
  h(
2201
2233
  "div",
@@ -4096,6 +4128,9 @@ var ACCOUNT_STYLES = `
4096
4128
  of ZERO - the pill collapsed to its dot in a flex header, with the label
4097
4129
  overflowing off screen. Nothing here queries its own width. */
4098
4130
  [part~='stack'] { container-type: normal; display: block; }
4131
+ /* A state in the trigger is a line already: no dot of its own, no action - the
4132
+ press opens the menu, where the gesture lives. */
4133
+ [part~='account-trigger-status'] [part~='status-action'] { display: none; }
4099
4134
  [part~='account-trigger'] {
4100
4135
  display: inline-flex;
4101
4136
  align-items: center;
@@ -4196,11 +4231,16 @@ var ACCOUNT_STYLES = `
4196
4231
  [part~='account-change'] { color: var(--_danger); }
4197
4232
  [part~='account-change']:hover { background: color-mix(in srgb, var(--_danger) 10%, transparent); }
4198
4233
  [part~='account-sep'] { height: 1px; background: var(--_border); margin: 0.2rem 0.3rem; }
4234
+ [part~='account-status'] { display: block; padding: 0 0.6rem 0.2rem; font-size: 0.8125rem; }
4199
4235
  [part~='account-error'] { margin: 0.15rem 0.55rem 0.3rem; font-size: 0.8125rem; color: var(--_danger); }
4200
4236
  `;
4201
4237
  var TICK_MS = 3e4;
4202
4238
  var SelfstoreAccountElement = class extends FlowWidget {
4239
+ static get observedAttributes() {
4240
+ return ["trigger"];
4241
+ }
4203
4242
  #confirm = null;
4243
+ #trigger = "destination";
4204
4244
  #open = false;
4205
4245
  #error = null;
4206
4246
  #timer = null;
@@ -4233,6 +4273,23 @@ var SelfstoreAccountElement = class extends FlowWidget {
4233
4273
  set confirmAction(fn) {
4234
4274
  this.#confirm = fn;
4235
4275
  }
4276
+ /**
4277
+ * What the trigger says: the DESTINATION it goes to (default), or the STATE
4278
+ * it is in.
4279
+ *
4280
+ * Naming the place is right when there is a choice - it makes local-first
4281
+ * visible without opening anything. It says nothing at all when an app
4282
+ * offers exactly one destination: "a file" where a file is the only thing it
4283
+ * could ever be. Such an app states a condition instead - saved, unsaved,
4284
+ * locked - which is the part that changes.
4285
+ */
4286
+ get trigger() {
4287
+ return this.#trigger;
4288
+ }
4289
+ set trigger(v) {
4290
+ this.#trigger = v === "status" ? "status" : "destination";
4291
+ this.rerender();
4292
+ }
4236
4293
  /** Whether the menu is on screen. Settable, so a host can close it from its
4237
4294
  * own router after the settings item navigated away. */
4238
4295
  get open() {
@@ -4244,8 +4301,16 @@ var SelfstoreAccountElement = class extends FlowWidget {
4244
4301
  this.watch();
4245
4302
  this.rerender();
4246
4303
  }
4304
+ attributeChangedCallback(name) {
4305
+ if (name === "trigger") {
4306
+ const value = this.attr(name);
4307
+ if (value !== null) this.trigger = value;
4308
+ }
4309
+ }
4247
4310
  connectedCallback() {
4248
4311
  super.connectedCallback();
4312
+ const trigger = this.attr("trigger");
4313
+ if (trigger !== null) this.trigger = trigger;
4249
4314
  this.follow(this.hostOf());
4250
4315
  }
4251
4316
  disconnectedCallback() {
@@ -4287,24 +4352,33 @@ var SelfstoreAccountElement = class extends FlowWidget {
4287
4352
  return kind === `destination.kind.${targetKind}` ? label ?? targetKind : kind;
4288
4353
  }
4289
4354
  /**
4290
- * The one line under the account. A plain "saved" is the only state this
4291
- * element words itself, because it is the only one with nothing to do about
4292
- * it - and the only one where WHEN is the whole information. Everything else
4293
- * (reconnect, unlock, changes to download) belongs to <selfstore-status>,
4294
- * which owns both the wording and the button that resolves it.
4355
+ * The one line this element words itself: a plain "saved", the only state
4356
+ * with nothing to do about it and the only one where WHEN is the whole
4357
+ * information. Null in every other state.
4295
4358
  */
4296
- line(host) {
4359
+ saved(host) {
4297
4360
  const { status, lastSavedAt } = host.engine.state;
4298
- if (status.state === "saved" && lastSavedAt && !("status.saved" in this.labels))
4299
- return h(
4300
- "span",
4301
- { part: "account-line" },
4302
- this.t("account.saved", { when: this.since(lastSavedAt, this.t("account.justNow")) })
4303
- );
4361
+ if (status.state !== "saved" || !lastSavedAt || "status.saved" in this.labels) return null;
4362
+ return h(
4363
+ "span",
4364
+ { part: "account-line" },
4365
+ this.t("account.saved", { when: this.since(lastSavedAt, this.t("account.justNow")) })
4366
+ );
4367
+ }
4368
+ /**
4369
+ * Anything that is NOT a plain "saved" - reconnect, unlock, changes to
4370
+ * export - belongs to <selfstore-status>, which owns both the wording and
4371
+ * the gesture. It goes under the card and across the whole menu, never
4372
+ * inside the card's text column: that column is sized for an address, and a
4373
+ * sentence wrapping in 167 px turned a three-word notice into 247 px of box.
4374
+ */
4375
+ attention(host) {
4376
+ if (this.saved(host)) return null;
4304
4377
  const el = document.createElement(
4305
4378
  siblingTag(this.localName, "account", "status")
4306
4379
  );
4307
- el.setAttribute("variant", "row");
4380
+ el.setAttribute("variant", "line");
4381
+ el.setAttribute("part", "account-status");
4308
4382
  el.labels = this.labels;
4309
4383
  el.icons = this.icons;
4310
4384
  el.store = this.store;
@@ -4338,6 +4412,23 @@ var SelfstoreAccountElement = class extends FlowWidget {
4338
4412
  this.rerender();
4339
4413
  }
4340
4414
  }
4415
+ /**
4416
+ * What the trigger reads. The destination by default; the state when an app
4417
+ * has only one destination to offer, where naming it says nothing that ever
4418
+ * changes. The state comes from <selfstore-status>'s own words, so the two
4419
+ * surfaces cannot drift.
4420
+ */
4421
+ said(host) {
4422
+ if (this.#trigger !== "status") return this.named(host);
4423
+ const el = document.createElement(
4424
+ siblingTag(this.localName, "account", "status")
4425
+ );
4426
+ el.setAttribute("variant", "line");
4427
+ el.setAttribute("part", "account-trigger-status");
4428
+ el.labels = this.labels;
4429
+ el.store = this.store;
4430
+ return el;
4431
+ }
4341
4432
  card(host) {
4342
4433
  const { targetKind } = host.engine.state;
4343
4434
  const icon = this.icons[targetKind];
@@ -4357,7 +4448,7 @@ var SelfstoreAccountElement = class extends FlowWidget {
4357
4448
  { part: "account-text" },
4358
4449
  h("span", { part: "account-title" }, this.named(host)),
4359
4450
  mail ? h("span", { part: "account-mail" }, mail) : null,
4360
- this.line(host)
4451
+ this.saved(host)
4361
4452
  )
4362
4453
  );
4363
4454
  }
@@ -4378,12 +4469,13 @@ var SelfstoreAccountElement = class extends FlowWidget {
4378
4469
  // The severity colors the DOT, never the label: a whole line turning
4379
4470
  // amber reads as a warning about the destination's name.
4380
4471
  h("span", { part: `account-dot sev-${status.severity}` }),
4381
- h("span", {}, this.named(host))
4472
+ h("span", { part: "account-trigger-text" }, this.said(host))
4382
4473
  );
4383
4474
  const menu = this.#open ? h(
4384
4475
  "div",
4385
4476
  { part: "card account-menu", role: "menu" },
4386
4477
  this.card(host),
4478
+ this.attention(host),
4387
4479
  h("div", { part: "account-sep" }),
4388
4480
  h(
4389
4481
  "button",