shadok-ai 0.15.34 → 0.15.35

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
@@ -76,9 +76,11 @@ Enter to skip; you can add it later from the web UI).
76
76
  - **Channels** — one `claude` process each, running in parallel. Renaming,
77
77
  grouping, and closing all sync live between the browser and Telegram: it's
78
78
  one list, server-owned, not two copies.
79
- - **Groups column** — on a desktop the groups get a column of their own, left
80
- of the agents: pick a group and the agents column shows only its agents
81
- (*Ungrouped* holds the rest, the home agent included unless you file it). Each
79
+ - **Groups column** — once you create a group, on a desktop the groups get a
80
+ column of their own, left of the agents (with no group there is no column, just
81
+ the single agents list): pick a group and the agents column shows only its
82
+ agents (*Ungrouped* holds the rest, the home agent included unless you file
83
+ it). Each
82
84
  group has an icon — drawn at random, double-click it to pick another: choose
83
85
  from the grid, type any emoji, or **type a word** (`rocket`, `bug`, `money`…)
84
86
  to search for a matching one — and a **+** that creates the new agent straight
@@ -86,9 +88,10 @@ Enter to skip; you can add it later from the web UI).
86
88
  A single click on a group (its icon or its row) selects it; this matters once
87
89
  **«** folds the column to its icons, where the icon is the whole row.
88
90
  Dragging an agent onto a group still files it there, and dragging groups
89
- reorders them. Double-click a name to rename it. A dot on a group you are not looking at says one of its agents
90
- is waiting for you, has an unread answer, or is working. On a phone nothing
91
- changes: the agents sheet still lists every group.
91
+ reorders them. Double-click a name to rename it. A group you are not looking at
92
+ carries the same signal its agents do — the row lights up (and breathes) when
93
+ one is working, waiting for you, or has an unread answer, the way a channel
94
+ does. On a phone nothing changes: the agents sheet still lists every group.
92
95
  - **Worktree isolation** — spawned agents get their own git worktree and branch
93
96
  by default, so parallel agents never collide. Work is never auto-discarded:
94
97
  an empty worktree is reclaimed on close, anything with changes or commits
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.15.34",
3
+ "version": "0.15.35",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test --test-timeout=120000 test/*.ts context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -590,15 +590,19 @@
590
590
  animation: breathe 1.8s ease-in-out infinite;
591
591
  }
592
592
  @media (prefers-reduced-motion: reduce) { .tab.working, .tab.has-bg { animation: none; } }
593
- #newTab, #newGroup {
593
+ #newTab, #newGroup, #newGroupBare {
594
594
  margin-top: 8px;
595
595
  border: 1px dashed var(--line);
596
596
  font-size: 12px;
597
597
  color: var(--text-dim);
598
598
  padding: 8px;
599
599
  }
600
- #newTab:hover, #newGroup:hover { color: var(--amber); border-color: var(--amber); }
600
+ #newTab:hover, #newGroup:hover, #newGroupBare:hover { color: var(--amber); border-color: var(--amber); }
601
601
  #newGroup { margin-top: 4px; }
602
+ /* The fallback "new group" entry: only on desktop, only while the rail (which
603
+ has its own) is hidden for want of a group. */
604
+ #newGroupBare { display: none; margin-top: 4px; }
605
+ @media (min-width: 641px) { body:not(.has-groups) #newGroupBare { display: block; } }
602
606
  /* The column's only coloured element: tabs are borderless rows and the + are
603
607
  ghost buttons, so a solid card reads as an action rather than as a session.
604
608
  Pinned to the bottom (margin-top:auto + sticky) so it stays reachable while
@@ -712,7 +716,10 @@
712
716
  #groupRail { display: none; }
713
717
  .icon-picker[hidden] { display: none; }
714
718
  @media (min-width: 641px) {
715
- #groupRail {
719
+ /* The column only earns its space once a group exists: with none, the
720
+ cockpit is the single agents list it always was (body.has-groups is set
721
+ from groups.length in applyView). */
722
+ body.has-groups #groupRail {
716
723
  width: 190px; flex: none;
717
724
  display: flex; flex-direction: column; gap: 2px;
718
725
  background: var(--bg-inset);
@@ -752,6 +759,15 @@
752
759
  .rail-item.unread .rail-dot { display: block; background: var(--phosphor); }
753
760
  .rail-item.working .rail-dot { display: block; background: var(--text-dim); animation: breathe 1.8s ease-in-out infinite; }
754
761
  @media (prefers-reduced-motion: reduce) { .rail-dot { animation: none !important; } }
762
+ /* A group carries the SAME effect its channels do — a working or finished
763
+ agent makes the whole group row read like that agent, not just a dot.
764
+ Mirrors .tab.working / .needs-answer / .unread. Shown for a group you are
765
+ NOT viewing (the active one shows its state on each agent in the column).
766
+ Placed after :hover so the state colour wins over the hover background. */
767
+ .rail-item.working { background: var(--amber-soft); animation: breathe 1.6s ease-in-out infinite; }
768
+ .rail-item.needs-answer { background: color-mix(in srgb, var(--amber) 28%, transparent); color: var(--text); }
769
+ .rail-item.unread { background: rgba(127, 201, 127, 0.14); border-left-color: var(--ok); color: var(--text); }
770
+ @media (prefers-reduced-motion: reduce) { .rail-item.working { animation: none !important; } }
755
771
  .rail-add, .rail-del {
756
772
  flex: none; display: none; border: none; background: none;
757
773
  padding: 0 3px; font-size: 13px; line-height: 1; color: var(--text-dim);
@@ -780,7 +796,11 @@
780
796
  body.rail-mini .rail-dot { position: absolute; top: 4px; right: 4px; }
781
797
 
782
798
  /* The agents column, narrowed to the selected group. */
783
- #tabbar .side-label .sl-all { display: none; }
799
+ /* With a column the list header is the selected group's name; with no
800
+ column it is just "Agents" again (the "Ungrouped" view label would read
801
+ oddly when there is nothing to be ungrouped from). */
802
+ body.has-groups #tabbar .side-label .sl-all { display: none; }
803
+ body:not(.has-groups) #tabbar .side-label .sl-view { display: none; }
784
804
  #tabbar .group-head { display: none; }
785
805
  #tabbar .group { margin-top: 0; }
786
806
  #tabbar .group-body { padding-left: 0; }
@@ -2568,6 +2588,11 @@
2568
2588
  <div id="groups"></div>
2569
2589
  <p id="viewEmpty" class="check-hint" hidden>No agent in this group yet — create one, or drag one onto the group.</p>
2570
2590
  <button id="newTab" title="New agent">+ new agent</button>
2591
+ <!-- The rail carries "+ new group" at its foot, but the rail is gone until a
2592
+ group exists — so this is the way to create the FIRST one. Desktop-only
2593
+ and only while there are no groups (CSS); it hands off to the rail's
2594
+ button once the column appears. -->
2595
+ <button id="newGroupBare" type="button" title="New agent group">+ new group</button>
2571
2596
  <div id="tweakSlot">
2572
2597
  <button id="tweakCta" type="button"
2573
2598
  title="Change shadok-ai itself — the agent works on a branch and opens a pull request">
@@ -4188,6 +4213,8 @@
4188
4213
 
4189
4214
  function applyView() {
4190
4215
  const v = currentView();
4216
+ // No group → no column (invariant: the rail earns its space, see the CSS).
4217
+ document.body.classList.toggle("has-groups", groups.length > 0);
4191
4218
  tabbar.dataset.view = v;
4192
4219
  for (const g of groups) g.el.classList.toggle("in-view", String(g.id) === v);
4193
4220
  const g = groups.find((x) => String(x.id) === v);
@@ -9786,12 +9813,18 @@
9786
9813
  markTweakTab(t);
9787
9814
  launchTab(t, { cwd: data.cwd, worktree: true, profile: "Shadok-Tweak" });
9788
9815
  });
9789
- newGroupBtn.addEventListener("click", () => {
9816
+ function addGroup() {
9790
9817
  const g = createGroup();
9791
9818
  persistGroups();
9819
+ // applyView first, so has-groups is set and the column is shown SYNCHRONOUSLY
9820
+ // — otherwise railShown() below is still false on the first group (created
9821
+ // from the bare button, while the rail was hidden) and it would not select.
9822
+ applyView();
9792
9823
  // A new group is where you are going next: select it (it starts empty).
9793
9824
  if (railShown()) setView(g.id, { follow: false });
9794
- });
9825
+ }
9826
+ newGroupBtn.addEventListener("click", addGroup);
9827
+ $("newGroupBare").addEventListener("click", addGroup);
9795
9828
 
9796
9829
  // ── Quota gauges (5h / 7d) ──────────────────────────────
9797
9830
  function fmtReset(sec) {