@frockbot/plugin-flock 0.3.8 → 0.3.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/plugin-flock",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -27,16 +27,16 @@
27
27
  "typecheck": "vue-tsc --noEmit -p tsconfig.json"
28
28
  },
29
29
  "dependencies": {
30
- "@frockbot/client-core": "0.3.8",
31
- "@frockbot/client-ui": "0.3.8",
32
- "@frockbot/configuration-core": "0.3.8",
33
- "@frockbot/kernel-contracts": "0.3.8",
34
- "@frockbot/plugin-shell": "0.3.8",
30
+ "@frockbot/client-core": "0.3.10",
31
+ "@frockbot/client-ui": "0.3.10",
32
+ "@frockbot/configuration-core": "0.3.10",
33
+ "@frockbot/kernel-contracts": "0.3.10",
34
+ "@frockbot/plugin-shell": "0.3.10",
35
35
  "cordis": "4.0.0-rc.8",
36
36
  "vue": "3.5.41"
37
37
  },
38
38
  "devDependencies": {
39
- "@frockbot/plugin-testkit": "0.3.8",
39
+ "@frockbot/plugin-testkit": "0.3.10",
40
40
  "@types/bun": "1.4.0",
41
41
  "@vitejs/plugin-vue": "6.0.8",
42
42
  "css-tree": "2.3.1",
@@ -89,7 +89,13 @@ onMounted(() => void flock.value.load());
89
89
  {{ flock.showArchived ? "Hide archived" : "Manage" }}
90
90
  </button>
91
91
  </div>
92
- <div v-if="flock.loading" class="flock-skeleton" aria-busy="true">
92
+ <!--
93
+ The skeleton is for a list nobody has yet, not for every request: the first
94
+ paint happens before `load()` is even called, and a reload after creating a
95
+ Bot must keep the list already on screen rather than blanking it. "No Bots
96
+ yet." is a fact about the account, so it waits for an answer.
97
+ -->
98
+ <div v-if="!flock.loaded" class="flock-skeleton" aria-busy="true">
93
99
  <span class="flock-skeleton-label">Loading your flock…</span>
94
100
  <div v-for="row in 3" :key="row" class="flock-skeleton-row">
95
101
  <UiSkeleton shape="circle" />
@@ -376,6 +376,38 @@ describe("Flock client reconciliation", () => {
376
376
  expect(state.value.overlay).toBeUndefined();
377
377
  expect(selected).not.toHaveLength(0);
378
378
  expect(selected.every((botId) => botId === createdBotId)).toBe(true);
379
+ // The User has just made a Bot; nothing on screen may say they have none.
380
+ // `loaded` is what the sidebar reads before it renders "No Bots yet.", and
381
+ // the created Bot is in the list by the time the dialog is gone.
382
+ expect(state.value.loaded).toBe(true);
383
+ expect(state.value.directory.bots.map((bot) => bot.botId)).toEqual([
384
+ createdBotId,
385
+ ]);
386
+ });
387
+
388
+ // The empty state is a fact about the account, and before the first read the
389
+ // account is unknown: the sidebar used to claim "No Bots yet." on the first
390
+ // paint, before `load()` had even been called.
391
+ test("does not claim an empty flock before the first read lands", async () => {
392
+ installStorage();
393
+ const state = mount((path) => {
394
+ if (path === "/api/bots")
395
+ return Promise.resolve({ schemaVersion: 1, revision: 1, bots: [] });
396
+ if (path === "/api/bots/lifecycles")
397
+ return Promise.resolve({ schemaVersion: 1, lifecycles: [] });
398
+ if (path === "/api/bots/identities")
399
+ return Promise.resolve({ schemaVersion: 1, identities: [] });
400
+ return Promise.reject(new Error(`unexpected request: ${path}`));
401
+ });
402
+
403
+ // Before any read: the account is unknown, so the sidebar keeps its
404
+ // skeleton rather than asserting the User has no Bots.
405
+ expect(state.value.loaded).toBe(false);
406
+
407
+ await state.value.load();
408
+
409
+ expect(state.value.loaded).toBe(true);
410
+ expect(state.value.directory.bots).toEqual([]);
379
411
  });
380
412
 
381
413
  test("archives with confirmation, hides archived Bots, and selects a fallback", async () => {
@@ -115,6 +115,7 @@ export const flockClientPlugin: ClientPlugin = (ctx) => {
115
115
  showArchived: false,
116
116
  showHidden: false,
117
117
  loading: false,
118
+ loaded: false,
118
119
  draftName: "",
119
120
  draftSheep: randomSheepRecipeV1(),
120
121
  bindShell(value) {
@@ -153,6 +154,7 @@ export const flockClientPlugin: ClientPlugin = (ctx) => {
153
154
  if (generation !== loadGeneration) return;
154
155
  authenticatedUserId = userId;
155
156
  state.value.directory = directory;
157
+ state.value.loaded = true;
156
158
  state.value.lifecycles = Object.fromEntries(
157
159
  lifecycleDirectory.lifecycles.map((item) => [
158
160
  item.botId,
@@ -503,9 +505,12 @@ export const flockClientPlugin: ClientPlugin = (ctx) => {
503
505
  }
504
506
  clearPendingCreate(userId);
505
507
  replacePreferredBot(command.botId);
506
- state.value.overlay = undefined;
508
+ // The list first, the dialog last. Tearing the dialog down before the
509
+ // reload landed showed the User the first-run empty state — "No Bots
510
+ // yet. Add your first sheep." — for the Bot they had just added.
507
511
  await state.value.load();
508
512
  await state.value.select(command.botId);
513
+ state.value.overlay = undefined;
509
514
  } catch (error) {
510
515
  if (isDefinitiveFlockFailure(error) && authenticatedUserId)
511
516
  clearPendingCreate(authenticatedUserId);
@@ -20,6 +20,14 @@ export interface FlockWebData {
20
20
  /** Per-Bot unread, as the Bot Durable Objects derive it. Never computed here. */
21
21
  unread: Record<string, BotUnreadViewV1>;
22
22
  loading: boolean;
23
+ /**
24
+ * Whether a directory read has ever completed.
25
+ *
26
+ * "No Bots yet." is a fact about the User's account, and it can only be
27
+ * stated once the account has been read. Before that — the first paint, and
28
+ * the reload after a Bot is created — the list is unknown, not empty.
29
+ */
30
+ loaded: boolean;
23
31
  error?: string;
24
32
  overlay?: "create" | "edit" | "archive";
25
33
  lifecycles: Record<string, BotLifecycleStatusV1>;