shadok-ai 0.9.7 → 0.9.8

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 CHANGED
@@ -144,13 +144,16 @@ Enter to skip; you can add it later from the web UI).
144
144
  rename, change profile, mirror to Telegram, close — and closing it brings the
145
145
  card back. Desktop only: the agents column is hidden on phones.
146
146
  - **Guided tour** — on a browser's first visit, a welcome card explains what
147
- shadok-ai is, then a spotlight walks the real controls: the agents column, an
148
- agent's ⋯ menu, the toolbar, the quota dials. Skip and Escape end it at any
149
- point; ⋯ → *Guided tour* replays it. A step whose target isn't on screen is
150
- **dropped rather than faked**, so the phone (no agents column) and an empty
151
- cockpit (no agent tab) get a shorter tour instead of a spotlight on nothing.
152
- On a brand-new instance it comes **before** the sign-in card being asked to
153
- authorise an OAuth flow before you know what the thing is takes it backwards.
147
+ shadok-ai is, then a spotlight walks the real controls: the agents list, an
148
+ agent's ⋯ menu, scheduled prompts, the toolbar, the quota dials. Skip and
149
+ Escape end it at any point; ⋯ → *Guided tour* replays it. A step whose target
150
+ isn't on screen is **dropped rather than faked** an empty cockpit has no
151
+ agent menu, so it gets a shorter tour instead of a spotlight on nothing. The
152
+ **phone gets the whole tour**, because a step can name the landmark on either
153
+ layout (the agents column *or* the channel picker) and is framed on whichever
154
+ one is rendered. On a brand-new instance it comes **before** the sign-in card
155
+ — being asked to authorise an OAuth flow before you know what the thing is
156
+ takes it backwards.
154
157
  - **A lead agent from the start** — an instance with no channel at all gets one:
155
158
  `general`, running the **Shadok-Boss** profile, in the launch directory and
156
159
  without a worktree. It is created at boot, or right after the first sign-in on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.9.7",
3
+ "version": "0.9.8",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts context/*/test/*.test.mjs",
@@ -13,6 +13,13 @@
13
13
  *
14
14
  * `target` is a CSS selector, an array of them (framed as one rectangle), or
15
15
  * null for the centred welcome card.
16
+ *
17
+ * An array is also how a step survives BOTH layouts: the phone and the desktop
18
+ * put the same landmark in different elements (the agents column becomes a
19
+ * `<select>`), and only one of the two is ever on screen. `unionRect` drops the
20
+ * absent one, so the group frames whichever exists. Before that, three of the
21
+ * six steps were silently filtered out on a phone — including every step that
22
+ * mentioned agents at all, i.e. the subject of the product.
16
23
  */
17
24
  export const TOUR_STEPS = [
18
25
  {
@@ -28,18 +35,34 @@ export const TOUR_STEPS = [
28
35
  id: "agents",
29
36
  title: "Your agents live here",
30
37
  body:
31
- "Start one with “new agent”, and group them once there are a few. " +
32
- "At the bottom, “Tweak Shadok-AI” starts an agent on the cockpit's own source — " +
33
- "it delivers its work as a pull request.",
34
- target: "#tabbar",
38
+ "Every agent gets its own git worktree and branch, so several never collide. " +
39
+ "Pick one here to follow it, and “+ New agent” starts another. One of them is this " +
40
+ "cockpit's home agent: the only one that cannot be closed.",
41
+ // The column on desktop, the channel picker on a phone — never both at once.
42
+ //
43
+ // Which is also why this body says neither "at the top" nor "at the bottom",
44
+ // and no longer points at "Tweak Shadok-AI": those describe the COLUMN, and
45
+ // the phone's `<select>` holds one option per agent plus "+ New agent" and
46
+ // nothing else. A tour that names a control the reader cannot find is the
47
+ // same failure as a spotlight on empty space, just harder to notice.
48
+ // (That Tweak is unreachable on a phone at all is a real gap — in the UI,
49
+ // not in the tour.)
50
+ target: ["#tabbar", "#chanSelect"],
35
51
  },
36
52
  {
37
53
  id: "tab",
38
54
  title: "Each agent has its own menu",
39
55
  body:
40
- "Theon an agent's tab is where its controls live: mute it, reload it, " +
41
- "rename it, change its profile, mirror it to Telegram, or close it.",
42
- target: ".tab.active",
56
+ "Thisholds an agent's controls: mute, reload, rename, change its profile, mirror it " +
57
+ "to a Telegram topic, or have it report to another agent when it finishes. " +
58
+ "“Context sent” shows exactly what shadok told it at spawn — and, except for the home " +
59
+ "agent, this is where you close it.",
60
+ // `#chanMenu`, NOT `.tab.active`. Two reasons, and the second is why it
61
+ // changed: it is literally the button this body tells you to press (the
62
+ // tab's own ⋯ opens the same menu), and it exists on BOTH layouts, whereas
63
+ // a phone has no `.tab` at all — so this step, and the schedule step below
64
+ // it, simply did not happen there.
65
+ target: "#chanMenu",
43
66
  },
44
67
  {
45
68
  // Deliberately its own stop, against this file's own "group landmarks"
@@ -53,24 +76,45 @@ export const TOUR_STEPS = [
53
76
  "“Schedule”, in that same ⋯ menu, pairs a recurring prompt with a shell check that runs " +
54
77
  "without the model. Silent check → nothing happened, the agent stays asleep at zero " +
55
78
  "tokens. Otherwise it wakes up holding the finding, and tells you here or on Telegram.",
56
- target: ".tab.active",
79
+ target: "#chanMenu",
57
80
  },
58
81
  {
59
82
  id: "tools",
60
83
  title: "The toolbar",
61
- // Named in the order the spotlight frames them, NOT drawn as glyphs: this
62
- // body is set with textContent, so an inline SVG icon would show as raw
63
- // markup and spelling the buttons out as emoji (🔑/👤/🔔) drifts the
64
- // moment those buttons stop being emoji, which is exactly what happened.
84
+ // Not drawn as glyphs: this body is set with textContent, so an inline SVG
85
+ // icon would show as raw markup and spelling the buttons out as emoji
86
+ // (🔑/👤/🔔) drifted the moment those buttons stopped being emoji.
87
+ //
88
+ // And no longer "left to right" either, which drifted the same way for the
89
+ // same reason: adding the ledger between secrets and profiles shifted every
90
+ // later name onto the wrong icon, so a reader counting along the row was
91
+ // told the ledger was "profiles". An order is a claim about the DOM that
92
+ // no test here can hold; a list of functions is not.
65
93
  body:
66
- "Left to right: secrets injected into agents that need them, profiles " +
67
- "(role, guardrails, model), Telegram settings, and the notification sound. " +
68
- "The holds the diff of what an agent changedand replays this tour.",
94
+ "The secret vault injected into agents that need it, the shared ledger of what your " +
95
+ "agents have already resolved, profiles (role, guardrails, model), Telegram, and the " +
96
+ "notification sound. On a narrow screen they fold into the which also holds the " +
97
+ "diff of an agent's work, the palette, and replays this tour.",
69
98
  // NOT `.hdr-tools`, which is `display: contents` on desktop and therefore
70
99
  // generates no box at all — its rect is all zeros, so the step would have
71
100
  // been dropped as "not visible" on the very layout where the toolbar is
72
101
  // most obvious. Framing the buttons themselves works on both layouts.
73
- target: ["#secretsBtn", "#profilesBtn", "#telegramBtn", "#muteNotif", "#moreBtn", "#starBtn"],
102
+ //
103
+ // Every tool is listed, including the ones `reflowHeaderTools` parks inside
104
+ // the closed ⋯ menu on a phone: those measure {0,0,0,0} and `unionRect`
105
+ // now drops them, so the group frames exactly what is on screen. Listing
106
+ // only some of them was the older bug in the other direction — `#usersBtn`
107
+ // sits left of `#secretsBtn` and stayed outside the spotlight.
108
+ target: [
109
+ "#usersBtn",
110
+ "#secretsBtn",
111
+ "#ledgerBtn",
112
+ "#profilesBtn",
113
+ "#telegramBtn",
114
+ "#muteNotif",
115
+ "#moreBtn",
116
+ "#starBtn",
117
+ ],
74
118
  },
75
119
  {
76
120
  id: "quota",
@@ -78,7 +122,8 @@ export const TOUR_STEPS = [
78
122
  body:
79
123
  "Your 5h and 7d subscription usage. The needle's centre is the pace that would " +
80
124
  "spend the window exactly on time, so leaning right means you're burning faster. " +
81
- "The version number next to the cockpit's name opens updates and the permission mode.",
125
+ "To the left, the cockpit's name is yours to change, and the version beside it opens " +
126
+ "updates and the permission mode.",
82
127
  target: ["#quota5h", "#quota7d"],
83
128
  },
84
129
  ];
@@ -93,13 +138,26 @@ export function visibleSteps(steps, isVisible) {
93
138
  *
94
139
  * Null rather than a zero rect: a caller that framed {0,0,0,0} would put the
95
140
  * spotlight on the page's top-left corner instead of skipping the step.
141
+ *
142
+ * EMPTY RECTS ARE NOT POINTS AT THE ORIGIN, and treating them as such is what
143
+ * broke the toolbar step on every phone. `reflowHeaderTools` moves five of the
144
+ * eight tool buttons into the closed ⋯ menu below 640px; a hidden element
145
+ * measures {0,0,0,0}, so the union's `Math.min` pinned top and left to zero and
146
+ * the spotlight stretched from the viewport's corner across the header —
147
+ * framing the brand, the version and both gauges while the body described
148
+ * buttons that were not on screen at all. Same family as the `.hdr-tools`
149
+ * zero-rect trap noted above, except that one dropped the step honestly and
150
+ * this one kept it and lied. Dropping the empty ones here means a group target
151
+ * frames whatever part of it is actually rendered, and yields null — hence a
152
+ * skipped step — only when none of it is.
96
153
  */
97
154
  export function unionRect(rects) {
98
- if (!rects || !rects.length) return null;
99
- const top = Math.min(...rects.map((r) => r.top));
100
- const left = Math.min(...rects.map((r) => r.left));
101
- const bottom = Math.max(...rects.map((r) => r.top + r.height));
102
- const right = Math.max(...rects.map((r) => r.left + r.width));
155
+ const real = (rects ?? []).filter((r) => r && r.width > 0 && r.height > 0);
156
+ if (!real.length) return null;
157
+ const top = Math.min(...real.map((r) => r.top));
158
+ const left = Math.min(...real.map((r) => r.left));
159
+ const bottom = Math.max(...real.map((r) => r.top + r.height));
160
+ const right = Math.max(...real.map((r) => r.left + r.width));
103
161
  return { top, left, width: right - left, height: bottom - top };
104
162
  }
105
163