@nelsonlongxiang/dsh-open-a2a-net 0.6.41 → 0.6.43

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.
Files changed (72) hide show
  1. package/README.md +2 -1
  2. package/README_zh.md +2 -1
  3. package/assets/nexusDist/assets/index-CaA76j0F.js +9 -0
  4. package/assets/nexusDist/index.html +1 -1
  5. package/assets/stageDist/assets/index-Bj7S2pA1.css +1 -1
  6. package/assets/stageDist/assets/index-C-28k_o5.js +974 -974
  7. package/assets/stageDist/index.html +12 -12
  8. package/cli/a2a.mjs +24 -4
  9. package/lib/canvas-store.js +249 -17
  10. package/lib/client.js +62 -72
  11. package/lib/client.js.map +1 -1
  12. package/lib/config.js +1 -0
  13. package/lib/faces/admin.js +103 -63
  14. package/lib/faces/wire.js +3 -1
  15. package/lib/index.js +116 -22
  16. package/lib/origin-claim.js +4 -0
  17. package/lib/skill/SKILL.md +117 -117
  18. package/lib/skill/references/collaboration.md +428 -428
  19. package/lib/skill/references/discovery.md +78 -78
  20. package/lib/skill/references/intake.md +107 -107
  21. package/lib/skill/references/membership.md +73 -73
  22. package/lib/skill/references/pitfalls.md +51 -51
  23. package/lib/skill/references/router.md +96 -96
  24. package/lib/skill/references/routing.md +65 -65
  25. package/lib/types/canvas-store.d.ts +48 -1
  26. package/lib/types/config.d.ts +2 -0
  27. package/lib/types/faces/admin.d.ts +2 -0
  28. package/lib/types/origin-claim.d.ts +2 -0
  29. package/nexus-stage/package.json +21 -21
  30. package/nexus-stage/scripts/mockhost.py +261 -261
  31. package/nexus-stage/src/canvas-ops.ts +191 -191
  32. package/nexus-stage/src/canvas-wire.ts +164 -164
  33. package/nexus-stage/src/cli-home-picker.ts +42 -42
  34. package/nexus-stage/src/fault.ts +56 -56
  35. package/nexus-stage/src/federation-overlay.ts +103 -103
  36. package/nexus-stage/src/federation.ts +241 -241
  37. package/nexus-stage/src/layout-doc.ts +145 -145
  38. package/nexus-stage/src/main.ts +520 -513
  39. package/nexus-stage/src/planning-card-text.ts +73 -73
  40. package/nexus-stage/src/planning-card.ts +63 -63
  41. package/nexus-stage/src/planning-edges.ts +26 -26
  42. package/nexus-stage/src/planning-fit.ts +45 -45
  43. package/nexus-stage/src/planning-frame-hue.ts +13 -13
  44. package/nexus-stage/src/planning-legend.ts +39 -39
  45. package/nexus-stage/src/planning-member-handles.ts +61 -61
  46. package/nexus-stage/src/planning-menus.ts +422 -422
  47. package/nexus-stage/src/planning-mirror.ts +75 -75
  48. package/nexus-stage/src/planning-nodelist.ts +294 -294
  49. package/nexus-stage/src/planning-overlap.ts +159 -159
  50. package/nexus-stage/src/planning-reachability.ts +28 -28
  51. package/nexus-stage/src/planning-relayout.ts +121 -121
  52. package/nexus-stage/src/planning-roster.ts +15 -15
  53. package/nexus-stage/src/planning-search.ts +168 -168
  54. package/nexus-stage/src/planning-types.ts +115 -115
  55. package/nexus-stage/src/planning-undo.ts +68 -68
  56. package/nexus-stage/src/planning-view.ts +1499 -1499
  57. package/nexus-stage/src/planning.css +340 -340
  58. package/nexus-stage/src/seat-name-editor.ts +31 -31
  59. package/nexus-stage/src/session-drag.ts +64 -64
  60. package/nexus-stage/src/viewport.ts +106 -106
  61. package/nexus-stage/src/wb-chat.ts +684 -684
  62. package/nexus-stage/src/wb-directory.ts +237 -237
  63. package/nexus-stage/src/wb-ledger.ts +279 -279
  64. package/nexus-stage/src/wb-names.ts +43 -43
  65. package/nexus-stage/src/wb-rail.ts +147 -147
  66. package/nexus-stage/src/workbench.css +388 -388
  67. package/nexus-stage/src/workbench.ts +1294 -1272
  68. package/nexus-stage/src/world.ts +509 -509
  69. package/nexus-stage/tsconfig.json +24 -24
  70. package/nexus-stage/vite.config.ts +15 -15
  71. package/package.json +1 -1
  72. package/assets/nexusDist/assets/index-NkNSeIx6.js +0 -9
@@ -1,340 +1,340 @@
1
- /* Planning mode (2D infinite canvas) — ported from the approved wireframe
2
- (feedback/2026-08-27-nexus-canvas-ux/prototype-v1.html) with the design
3
- system's three-layer token discipline. Tokens are scoped to #dsh-plan
4
- (token sovereignty: the stage owns its theme, detached from any host).
5
- Deviations from the prototype, all for computable geometry:
6
- - .frame-head is horizontally centered with a fixed 24px border-box
7
- height, so the star-edge origin is exactly (fx + fw/2, fy + 11);
8
- - .node has a fixed 56px border-box height matching world.NODE_H, with
9
- nowrap/ellipsis so the card can never outgrow the edge math.
10
- Layers (z): grid 0 < world 1 < marquee 2 < HUD 3. */
11
-
12
- #dsh-plan *,#dsh-plan *::before,#dsh-plan *::after{box-sizing:border-box}
13
-
14
- #dsh-plan{
15
- /* primitive — bound to the workbench tokens (--n-*, workbench.css) so the
16
- board follows the same dark/light theme as the rail; the literal after
17
- each comma is the stage's own dark value, kept so the board still
18
- renders on a host that ships without workbench.css (token sovereignty). */
19
- --uupm-ink-0:var(--n-ink,#dde3f0); --uupm-ink-1:var(--n-ink-muted,#7a86a0);
20
- --uupm-bg-0:var(--n-bg,#060a12); --uupm-bg-1:var(--n-panel,#0d1322); --uupm-bg-2:var(--n-raised,#131b2e);
21
- --uupm-accent:var(--n-accent,#9673f7); --uupm-live:var(--n-live,#4ade80); --uupm-cold:var(--n-cold,#7a86a0);
22
- --uupm-edge-member:var(--n-canvas,#22d3ee);
23
- --uupm-warn:var(--n-act,#f59e0b); --uupm-error:var(--n-danger,#f87171); --uupm-indigo:var(--n-federal,#6366f1);
24
- /* accent washes (hover 8% / active 12-18% / tint 5-10%), one definition each */
25
- --uupm-wash-5:color-mix(in srgb,var(--uupm-accent) 5%,transparent);
26
- --uupm-wash-8:color-mix(in srgb,var(--uupm-accent) 8%,transparent);
27
- --uupm-wash-12:color-mix(in srgb,var(--uupm-accent) 12%,transparent);
28
- --uupm-wash-15:color-mix(in srgb,var(--uupm-accent) 15%,transparent);
29
- --uupm-wash-18:color-mix(in srgb,var(--uupm-accent) 18%,transparent);
30
- /* semantic */
31
- --surface:var(--uupm-bg-1); --ink:var(--uupm-ink-0); --ink-muted:var(--uupm-ink-1);
32
- --accent:var(--uupm-accent); --edge-live:var(--uupm-live); --edge-cold:var(--uupm-cold);
33
- --federal:var(--uupm-indigo);
34
- /* component */
35
- --card-bg:var(--surface); --card-line:var(--n-card-line,#5c6680); --card-line-hover:var(--uupm-ink-1);
36
- --toolbar-bg:color-mix(in srgb,var(--uupm-bg-1) 92%,transparent);
37
- --grid-dot:color-mix(in srgb,var(--uupm-ink-1) 22%,transparent);
38
- --scrim:color-mix(in srgb,var(--uupm-bg-0) 60%,transparent);
39
- --error-ink:var(--n-error-ink,#fecaca); --error-panel:var(--n-error-panel,#2a0d10f2);
40
- }
41
-
42
- #dsh-plan{
43
- position:absolute; inset:0; overflow:hidden; background:var(--uupm-bg-0); color:var(--ink);
44
- font-size:13px; line-height:1.5; outline:none;
45
- }
46
- #dsh-plan .mono{font-family:ui-monospace,Cascadia,Consolas,monospace}
47
- #dsh-plan:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
48
-
49
- /* ── dot-grid world layer: screen-fixed, positioned by JS from the viewport ── */
50
- #dsh-plan .p-grid{position:absolute; inset:0; z-index:0; pointer-events:none;
51
- background-image:radial-gradient(circle, var(--grid-dot) 1px, transparent 1px);
52
- background-size:24px 24px; background-position:0 0}
53
-
54
- /* ── transformed world container ── */
55
- #dsh-plan .p-world{position:absolute; inset:0; z-index:1; transform-origin:0 0}
56
- #dsh-plan .p-layer{position:absolute; inset:0; pointer-events:none}
57
- #dsh-plan .p-edges{position:absolute; overflow:visible; width:100%; height:100%; pointer-events:none}
58
- /* The team frame is a grouping surface, not an interactive one: left
59
- event-transparent so real drags reach the member cards inside it (the
60
- frame head opts back in for group-drag/menu/Delete). */
61
- #dsh-plan .p-frame{pointer-events:none}
62
- #dsh-plan .p-frame-head{pointer-events:auto}
63
- #dsh-plan .p-node{pointer-events:auto}
64
- #dsh-plan .e-member{stroke:var(--uupm-edge-member); stroke-opacity:.38; stroke-width:1.5}
65
- #dsh-plan .e-member.dashed{stroke-dasharray:3 4}
66
-
67
- /* ── team frame ── */
68
- /* --frame-line is the team hue set inline per frame (frameHue); --frame-ink is
69
- what the frame actually paints with — the hue itself on dark, shaded 72%
70
- toward black on light, where amber/lime/teal outlines fell to 1.8-2.2:1. */
71
- #dsh-plan .p-frame{--frame-ink:var(--frame-line,#8B5CF6)}
72
- @media (prefers-color-scheme: light){
73
- #dsh-plan .p-frame{--frame-ink:color-mix(in srgb,var(--frame-line,#8B5CF6) 72%,black)}
74
- }
75
- #dsh-plan .p-frame{position:absolute; border:1px solid var(--frame-ink); border-radius:10px;
76
- background:var(--frame-bg,var(--uupm-wash-5)); min-width:120px; min-height:60px}
77
- #dsh-plan .p-frame-head{position:absolute; top:-13px; left:50%; transform:translateX(-50%);
78
- height:24px; padding:0 10px; border-radius:6px; display:flex; gap:8px; align-items:center;
79
- background:var(--uupm-bg-2); border:1px solid var(--frame-ink);
80
- font-size:12px; font-weight:600; letter-spacing:.02em; white-space:nowrap; cursor:grab}
81
- #dsh-plan .p-frame-head:active{cursor:grabbing}
82
- #dsh-plan .p-frame-head:hover{border-color:var(--card-line-hover)}
83
- #dsh-plan .p-frame-head .cnt{color:var(--ink-muted); font-weight:400}
84
- #dsh-plan .p-frame-head .route{font-size:10.5px; color:var(--ink-muted)}
85
- #dsh-plan .p-frame.is-mirror{border-style:dashed}
86
- #dsh-plan .p-frame.is-roster{border-style:double;border-width:3px}
87
- #dsh-plan .p-frame.is-roster .p-frame-head{border-style:dashed}
88
- #dsh-plan .p-frame.is-syncing .p-frame-head .route{color:var(--accent)}
89
- #dsh-plan .p-frame.is-unreachable{opacity:.76;border-style:dashed}
90
- #dsh-plan .p-frame-unreachable{position:absolute;left:8px;top:20px;max-width:calc(100% - 16px);color:var(--uupm-error);font-size:11px;pointer-events:none}
91
- #dsh-plan .p-node.remote.is-unreachable{opacity:.72;border-left-color:var(--uupm-error)}
92
- #dsh-plan .p-node.remote.is-unreachable .sub{color:var(--uupm-error)}
93
-
94
- /* ── node card (fixed 56px border-box height = world.NODE_H) ── */
95
- #dsh-plan .p-node{position:absolute; width:172px; height:56px; padding:8px 26px 8px 14px;
96
- background:var(--card-bg); border:1px solid var(--card-line); border-left:3px solid var(--edge-live);
97
- border-radius:8px; cursor:grab; user-select:none;
98
- transition:border-color 120ms cubic-bezier(0.2,0,0,1), transform 120ms cubic-bezier(0.2,0,0,1)}
99
- #dsh-plan .p-node:hover{border-color:var(--card-line-hover)}
100
- #dsh-plan .p-node:active{cursor:grabbing}
101
- #dsh-plan .p-node:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
102
- #dsh-plan .p-node.selected{outline:2px solid var(--accent); outline-offset:1px}
103
- #dsh-plan .p-node.dragging{transform:scale(1.02); border-color:var(--accent)}
104
- #dsh-plan .p-node.cold{border-left-color:var(--edge-cold)}
105
- #dsh-plan .p-node .nm{font-weight:600; display:flex; align-items:center; gap:7px;
106
- overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
107
- /* The name span clips with an ellipsis (min-width:0 lets it shrink inside
108
- the flex row) — without it a long peer address was hard-cut mid-digit.
109
- 属主令 W2: TWO-LINE clamp for the name (long Chinese team titles read
110
- fully); the card's 172x56 geometry is untouched — the clamp absorbs the
111
- overflow. The handle line (.sub) stays single-line ellipsis below. */
112
- #dsh-plan .p-node .nm-text{min-width:0; overflow:hidden; white-space:normal;
113
- display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; line-height:1.25}
114
- /* A peer address has no break point, so the two-line clamp clipped it to one
115
- line under the badge; let it wrap anywhere onto the second line. */
116
- #dsh-plan .p-node.remote .nm-text{overflow-wrap:anywhere}
117
- #dsh-plan .p-node .dot{width:8px; height:8px; border-radius:50%; background:var(--edge-live); flex:none}
118
- #dsh-plan .p-node.cold .dot{background:transparent; border:1.5px solid var(--edge-cold)}
119
- #dsh-plan .p-node .sub-row{display:flex; align-items:baseline; gap:6px; margin-top:2px; min-width:0}
120
- #dsh-plan .p-node .sub-row .sub{flex:1 1 auto; min-width:0; margin-top:0}
121
- #dsh-plan .p-node .work{flex:none; font-size:10.5px; font-weight:500; color:var(--ink-muted); white-space:nowrap}
122
- #dsh-plan .p-node .work:empty{display:none}
123
- #dsh-plan .p-node.is-working .work{color:var(--edge-live)}
124
- #dsh-plan .p-node.is-working .dot{animation:p-working 1.4s ease-in-out infinite}
125
- @keyframes p-working{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--edge-live) 55%,transparent)}50%{box-shadow:0 0 0 4px transparent}}
126
- @media (prefers-reduced-motion: reduce){#dsh-plan .p-node.is-working .dot{animation:none}}
127
- #dsh-plan .p-node .sub{font-size:11px; color:var(--ink-muted); margin-top:2px;
128
- overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
129
- /* W1: a picked search hit pulses its card for 1.2s (class removed on timer). */
130
- #dsh-plan .p-node.pulse{box-shadow:0 0 0 2px var(--accent), 0 0 14px 4px var(--uupm-wash-15); border-color:var(--accent)}
131
- /* W1: toolbar search box + hit list. */
132
- #dsh-plan .p-search{position:relative}
133
- #dsh-plan .p-search-input{width:170px; min-height:28px; padding:2px 8px; font-size:12px;
134
- background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px}
135
- #dsh-plan .p-search-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
136
- #dsh-plan .p-search-list{position:absolute; top:100%; left:0; margin-top:4px; display:none;
137
- min-width:240px; max-height:220px; overflow:auto; z-index:9;
138
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:6px}
139
- #dsh-plan .p-search-hit{padding:5px 10px; font-size:12px; color:var(--ink); cursor:pointer; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
140
- #dsh-plan .p-search-hit:hover, #dsh-plan .p-search-hit.active{background:var(--uupm-wash-15)}
141
- #dsh-plan .p-node .prio{position:absolute; top:6px; right:8px; font-size:10px; color:var(--ink-muted);
142
- border:1px solid var(--card-line); border-radius:4px; padding:0 5px}
143
-
144
- /* ── marquee ── */
145
- #dsh-plan .p-marquee{position:absolute; z-index:2; display:none; pointer-events:none;
146
- border:1px solid var(--accent); background:var(--uupm-wash-8)}
147
-
148
- /* ── HUD ── */
149
- #dsh-plan .p-toolbar{position:absolute; top:16px; left:16px; z-index:3; display:flex; gap:6px; align-items:center;
150
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:6px 8px}
151
- #dsh-plan .p-tabs{position:absolute; top:16px; left:50%; transform:translateX(-50%); z-index:3; display:flex; gap:4px;
152
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px}
153
- #dsh-plan .p-tabs [role=tab]{background:transparent; color:var(--ink-muted); border:1px solid transparent; border-radius:6px;
154
- padding:4px 12px; font:inherit; cursor:pointer}
155
- #dsh-plan .p-tabs [role=tab]:hover{color:var(--ink); border-color:var(--card-line)}
156
- #dsh-plan .p-tabs [role=tab][aria-selected=true]{color:var(--ink); border-color:var(--accent); background:var(--uupm-wash-12)}
157
- #dsh-plan .p-tabs [role=tab]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
158
- #dsh-plan .p-toolbar button{background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px;
159
- padding:4px 10px; font:inherit; cursor:pointer; white-space:nowrap}
160
- #dsh-plan .p-toolbar button:hover{border-color:var(--accent)}
161
- #dsh-plan .p-toolbar button:active{background:var(--uupm-wash-15)}
162
- #dsh-plan .p-toolbar button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
163
- #dsh-plan .p-zoom{min-width:44px; text-align:center; font-size:11px; color:var(--ink-muted)}
164
- /* Toolbar groups (view | arrange | network): a hairline between them. */
165
- #dsh-plan .p-sep{align-self:stretch; width:1px; margin:2px 4px; background:var(--card-line)}
166
- #dsh-plan .p-lamp{display:flex; align-items:center; gap:6px; font-size:11px; color:var(--ink-muted); padding:0 6px}
167
- #dsh-plan .p-lamp i{width:7px; height:7px; border-radius:50%; background:var(--ink-muted); transition:background 140ms ease}
168
- #dsh-plan .p-lamp.saved i{background:var(--edge-live)}
169
- #dsh-plan .p-lamp.pending i{background:var(--uupm-warn)}
170
- #dsh-plan .p-lamp.error{color:var(--uupm-error); cursor:pointer}
171
- #dsh-plan .p-lamp.error i{background:var(--uupm-error)}
172
- #dsh-plan .p-lamp.error:hover{border:1px solid var(--uupm-error); border-radius:4px}
173
-
174
- #dsh-plan .p-status{position:absolute; top:16px; right:16px; z-index:3; display:flex; gap:14px;
175
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:8px 14px; font-size:12px}
176
- #dsh-plan .p-status b{font-weight:600}
177
- #dsh-plan .ok{color:var(--edge-live)} #dsh-plan .warn{color:var(--uupm-warn)} #dsh-plan .mut{color:var(--ink-muted)}
178
-
179
- #dsh-plan .p-legend{position:absolute; bottom:16px; left:16px; z-index:3; font-size:11px; color:var(--ink-muted);
180
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px 10px}
181
- #dsh-plan .p-legend summary{cursor:pointer; padding:2px 0; user-select:none}
182
- #dsh-plan .p-legend summary:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
183
- #dsh-plan .p-legend[open]{padding-bottom:8px}
184
- #dsh-plan .p-legend-rows{display:grid; gap:4px; margin-top:4px}
185
- #dsh-plan .p-legend i{display:inline-block; width:18px; border-top:2px solid var(--uupm-edge-member);
186
- vertical-align:middle; margin-right:6px}
187
- #dsh-plan .p-legend i.dashed{border-top-style:dashed}
188
-
189
- /* ── narrow board (<680px): the HUD stacks instead of colliding ──
190
- The toolbar, status chip and legend are absolutely placed for a wide
191
- board; on a phone they overlapped each other and the toolbar's CJK
192
- labels wrapped one glyph per line. The one-row sideways scroll that
193
- followed hid the search box and half the actions off-screen (review
194
- 2026-10-27), so the toolbar and status chip now flow at the top of the
195
- board instead: the toolbar wraps, search first on its own row, and the
196
- status chip sits under whatever height that takes. The legend (reference,
197
- not state) hides. */
198
- @media (max-width:680px){
199
- #dsh-plan .p-toolbar{position:relative; top:auto; left:auto; margin:8px 8px 0; flex-wrap:wrap; row-gap:6px}
200
- #dsh-plan .p-toolbar > *{flex:none}
201
- #dsh-plan .p-toolbar .p-search{order:-1; flex:1 1 100%}
202
- #dsh-plan .p-toolbar .p-sep{display:none}
203
- #dsh-plan .p-toolbar .p-search-input{width:100%; box-sizing:border-box}
204
- #dsh-plan .p-status{position:relative; top:auto; right:auto; left:auto; width:fit-content; margin:6px 8px 0; padding:6px 10px; gap:10px; flex-wrap:wrap}
205
- #dsh-plan .p-legend{display:none}
206
- }
207
- @media (pointer:coarse){
208
- #dsh-plan .p-toolbar button{min-height:36px; min-width:36px}
209
- }
210
-
211
- /* ── reduced motion: zero animation, state carried by text (hard baseline) ── */
212
- @media (prefers-reduced-motion: reduce){
213
- #dsh-plan *{transition:none !important; animation:none !important}
214
- }
215
-
216
- /* ── PR C: drop-target highlight, context menu, name dialog, notices ── */
217
- #dsh-plan .p-frame.drop-target{border-color:var(--accent); border-width:2px; background:var(--uupm-wash-12)}
218
- #dsh-plan .p-frame-head:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
219
-
220
- /* ── PR D: federation overlay — peer badges, activity/federal edges, status fly ── */
221
- /* Peer cards: the 'peer' tag is ~36px wide against the 26px gutter a P0–P9
222
- tag fits in, so it sat on top of the address text — widen the gutter. */
223
- #dsh-plan .p-node.remote{border-left-color:var(--federal); padding-right:46px}
224
- #dsh-plan .p-node.remote .dot{background:var(--federal)}
225
- #dsh-plan .p-node.remote .prio{color:var(--ink)}
226
- /* gh#20 W-A link badge: linked green / stranger gray (fleet reserved);
227
- the ✓/✗ verified mark rides inside the same chip. */
228
- #dsh-plan .p-node .prio.lnk-linked{color:var(--live, #2e9e5b); border-color:var(--live, #2e9e5b)}
229
- #dsh-plan .p-node .prio.lnk-stranger{color:var(--ink-muted); border-color:var(--card-line)}
230
- #dsh-plan .e-activity{stroke:var(--accent); stroke-width:2; stroke-dasharray:8 6; stroke-opacity:.9}
231
- #dsh-plan .e-federal{stroke:var(--federal); stroke-width:1.5; stroke-dasharray:6 5; stroke-opacity:.5}
232
- #dsh-plan .edge-label{font-size:10.5px; fill:var(--ink-muted)}
233
- /* in-flight routes flow only when motion is welcome; reduced-motion keeps
234
- the zero-animation baseline (state stays in the label text). */
235
- @media (prefers-reduced-motion: no-preference){
236
- #dsh-plan .e-activity{animation:p-activity-flow 1.1s linear infinite}
237
- }
238
- @keyframes p-activity-flow{to{stroke-dashoffset:-28}}
239
- #dsh-plan .p-status{cursor:pointer}
240
- #dsh-plan .p-status:hover{border-color:var(--accent)}
241
- #dsh-plan .p-status:active{background:var(--uupm-wash-12)}
242
- #dsh-plan .p-status:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
243
-
244
- #dsh-plan .p-menu{position:fixed; z-index:5; min-width:170px; display:grid; padding:4px; gap:2px;
245
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px}
246
- #dsh-plan .p-menu [role=menuitem]{background:transparent; color:var(--ink); border:1px solid transparent;
247
- border-radius:6px; padding:6px 10px; font:inherit; text-align:left; cursor:pointer; white-space:nowrap}
248
- #dsh-plan .p-menu [role=menuitem]:hover{border-color:var(--card-line); background:var(--uupm-wash-8)}
249
- #dsh-plan .p-menu [role=menuitem]:active{background:var(--uupm-wash-18)}
250
- #dsh-plan .p-menu [role=menuitem]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
251
- #dsh-plan .p-menu [role=menuitem][aria-disabled=true]{color:var(--ink-muted); cursor:not-allowed}
252
-
253
- #dsh-plan .p-dialog{position:absolute; inset:0; z-index:6; display:grid; place-items:center;
254
- background:var(--scrim)}
255
- #dsh-plan .p-dialog-panel{min-width:300px; padding:14px 16px; display:grid; gap:10px;
256
- background:var(--uupm-bg-2); border:1px solid var(--card-line); border-radius:10px}
257
- #dsh-plan .p-dialog-title{font-weight:600; font-size:12.5px; color:var(--ink)}
258
- #dsh-plan .p-dialog-input{font:inherit; color:var(--ink); background:var(--uupm-bg-0);
259
- border:1px solid var(--card-line); border-radius:6px; padding:7px 10px}
260
- #dsh-plan .p-dialog-input:hover{border-color:color-mix(in srgb,var(--uupm-ink-1) 55%,transparent)}
261
- #dsh-plan .p-dialog-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px; border-color:var(--accent)}
262
- #dsh-plan .p-dialog-err{min-height:1.2em; font-size:11px; color:var(--uupm-error)}
263
- #dsh-plan .p-dialog-kind{display:grid; grid-template-columns:1fr 1fr; gap:4px; padding:3px;
264
- background:var(--uupm-bg-0); border:1px solid var(--card-line); border-radius:8px}
265
- #dsh-plan .p-dialog-kind button{font:inherit; color:var(--ink-muted); background:transparent; border:1px solid transparent;
266
- border-radius:6px; padding:6px 10px; cursor:pointer}
267
- #dsh-plan .p-dialog-kind button:hover{color:var(--ink); background:var(--uupm-wash-8)}
268
- #dsh-plan .p-dialog-kind button[aria-checked=true]{color:var(--ink); font-weight:600; background:var(--uupm-wash-15);
269
- border-color:var(--accent)}
270
- #dsh-plan .p-dialog-kind button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
271
- #dsh-plan .p-dialog-hint{font-size:11px; color:var(--ink-muted); max-width:340px}
272
- #dsh-plan .p-dialog-row{display:flex; gap:8px; justify-content:flex-end}
273
- #dsh-plan .p-dialog-row button{background:transparent; color:var(--ink); border:1px solid var(--card-line);
274
- border-radius:6px; padding:5px 12px; font:inherit; cursor:pointer}
275
- #dsh-plan .p-dialog-row button:hover{border-color:var(--accent)}
276
- #dsh-plan .p-dialog-row button:active{background:var(--uupm-wash-15)}
277
- #dsh-plan .p-dialog-row button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
278
-
279
- #dsh-plan .p-notice-stack{position:absolute; bottom:52px; left:50%; transform:translateX(-50%); z-index:5;
280
- display:grid; gap:6px; justify-items:center; pointer-events:none}
281
- #dsh-plan .p-notice{max-width:60ch; padding:7px 12px; font-size:12px; color:var(--ink);
282
- background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; white-space:nowrap;
283
- overflow:hidden; text-overflow:ellipsis}
284
- #dsh-plan .p-notice.error{color:var(--error-ink); border-color:var(--uupm-error); background:var(--error-panel)}
285
-
286
- /* Panel-slim migration: the unjoined-sessions join affordance on the toolbar. */
287
- #dsh-plan .p-netbtn{color:var(--ink-muted)}
288
- #dsh-plan .p-netbtn:not([disabled]){color:var(--ink)}
289
- #dsh-plan .p-netmenu{max-height:240px; overflow:auto}
290
-
291
- /* ── Node list drawer (network-membership-display): teamed nodes hold the
292
- canvas; every other session is managed here, switched by host. ── */
293
- #dsh-plan .p-nodebtn{color:var(--ink)}
294
- #dsh-plan .p-nodelist{min-width:300px; max-width:340px; max-height:420px; overflow:auto; padding:6px}
295
- #dsh-plan .p-nodelist-head{display:flex; align-items:center; justify-content:space-between; gap:8px;
296
- padding:2px 4px 6px; border-bottom:1px solid var(--card-line)}
297
- #dsh-plan .p-nodelist-title{font-size:12px; font-weight:600; color:var(--ink)}
298
- #dsh-plan .p-nodesel{font:inherit; font-size:11.5px; color:var(--ink); background:var(--uupm-bg-0);
299
- border:1px solid var(--card-line); border-radius:6px; padding:2px 6px; max-width:220px}
300
- #dsh-plan .p-nodesel:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
301
- #dsh-plan .p-nodelist-group{font-size:11px; color:var(--ink-muted); padding:8px 4px 3px;
302
- display:flex; align-items:baseline; gap:8px}
303
- #dsh-plan .p-nodelist-note{font-size:10px; color:var(--ink-muted); opacity:.8}
304
- #dsh-plan .p-noderow{display:flex; align-items:center; gap:6px; padding:3px 4px; border-radius:6px;
305
- font-size:12px; color:var(--ink)}
306
- #dsh-plan .p-noderow:hover{background:var(--uupm-wash-8)}
307
- #dsh-plan .p-nodedot{flex:none; width:7px; height:7px; border-radius:50%; background:var(--edge-live)}
308
- #dsh-plan .p-nodedot.cold{background:var(--ink-muted)}
309
- #dsh-plan .p-nodedot.off{background:transparent; border:1px solid var(--ink-muted)}
310
- #dsh-plan .p-nodename{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
311
- #dsh-plan .p-nonet{flex:none; font-size:10px; color:var(--uupm-warn); border:1px solid var(--uupm-warn);
312
- border-radius:4px; padding:0 4px}
313
- #dsh-plan .p-teamcount{flex:none; font-size:10px; color:var(--ink-muted); border:1px solid var(--card-line);
314
- border-radius:4px; padding:0 4px}
315
- #dsh-plan .p-listbtn{flex:none; background:transparent; color:var(--ink); border:1px solid var(--card-line);
316
- border-radius:5px; padding:1px 7px; font:inherit; font-size:11px; cursor:pointer}
317
- #dsh-plan .p-listbtn:hover{border-color:var(--card-line-hover); background:var(--uupm-wash-8)}
318
- #dsh-plan .p-listbtn:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
319
- #dsh-plan .p-nodelist-empty{padding:10px 4px; font-size:11.5px; color:var(--ink-muted)}
320
-
321
- /* ── Frame resize handles (owner ruling: frames stretch/adjust) — eight
322
- invisible hit zones that show the grip on hover. ── */
323
- #dsh-plan .p-frame{position:absolute}
324
- #dsh-plan .p-frame-handle{position:absolute; pointer-events:auto; width:10px; height:10px; z-index:2; opacity:0;
325
- transition:opacity 120ms ease}
326
- #dsh-plan .p-frame:hover .p-frame-handle{opacity:1}
327
- #dsh-plan .p-frame-handle::after{content:''; position:absolute; inset:2px; background:var(--frame-ink);
328
- border-radius:2px}
329
- #dsh-plan .p-h-n{top:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
330
- #dsh-plan .p-h-s{bottom:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
331
- #dsh-plan .p-h-e{right:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
332
- #dsh-plan .p-h-w{left:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
333
- #dsh-plan .p-h-ne{top:-5px; right:-5px; cursor:nesw-resize}
334
- #dsh-plan .p-h-nw{top:-5px; left:-5px; cursor:nwse-resize}
335
- #dsh-plan .p-h-se{bottom:-5px; right:-5px; cursor:nwse-resize}
336
- #dsh-plan .p-h-sw{bottom:-5px; left:-5px; cursor:nesw-resize}
337
- #dsh-plan .p-h-ne::after, #dsh-plan .p-h-sw::after{transform:rotate(45deg)}
338
- #dsh-plan .p-h-nw::after, #dsh-plan .p-h-se::after{transform:rotate(-45deg)}
339
- #dsh-plan .p-frame.selected .p-frame-handle{opacity:1}
340
- #dsh-plan .p-frame.selected{outline:1px solid var(--frame-ink)}
1
+ /* Planning mode (2D infinite canvas) — ported from the approved wireframe
2
+ (feedback/2026-08-27-nexus-canvas-ux/prototype-v1.html) with the design
3
+ system's three-layer token discipline. Tokens are scoped to #dsh-plan
4
+ (token sovereignty: the stage owns its theme, detached from any host).
5
+ Deviations from the prototype, all for computable geometry:
6
+ - .frame-head is horizontally centered with a fixed 24px border-box
7
+ height, so the star-edge origin is exactly (fx + fw/2, fy + 11);
8
+ - .node has a fixed 56px border-box height matching world.NODE_H, with
9
+ nowrap/ellipsis so the card can never outgrow the edge math.
10
+ Layers (z): grid 0 < world 1 < marquee 2 < HUD 3. */
11
+
12
+ #dsh-plan *,#dsh-plan *::before,#dsh-plan *::after{box-sizing:border-box}
13
+
14
+ #dsh-plan{
15
+ /* primitive — bound to the workbench tokens (--n-*, workbench.css) so the
16
+ board follows the same dark/light theme as the rail; the literal after
17
+ each comma is the stage's own dark value, kept so the board still
18
+ renders on a host that ships without workbench.css (token sovereignty). */
19
+ --uupm-ink-0:var(--n-ink,#dde3f0); --uupm-ink-1:var(--n-ink-muted,#7a86a0);
20
+ --uupm-bg-0:var(--n-bg,#060a12); --uupm-bg-1:var(--n-panel,#0d1322); --uupm-bg-2:var(--n-raised,#131b2e);
21
+ --uupm-accent:var(--n-accent,#9673f7); --uupm-live:var(--n-live,#4ade80); --uupm-cold:var(--n-cold,#7a86a0);
22
+ --uupm-edge-member:var(--n-canvas,#22d3ee);
23
+ --uupm-warn:var(--n-act,#f59e0b); --uupm-error:var(--n-danger,#f87171); --uupm-indigo:var(--n-federal,#6366f1);
24
+ /* accent washes (hover 8% / active 12-18% / tint 5-10%), one definition each */
25
+ --uupm-wash-5:color-mix(in srgb,var(--uupm-accent) 5%,transparent);
26
+ --uupm-wash-8:color-mix(in srgb,var(--uupm-accent) 8%,transparent);
27
+ --uupm-wash-12:color-mix(in srgb,var(--uupm-accent) 12%,transparent);
28
+ --uupm-wash-15:color-mix(in srgb,var(--uupm-accent) 15%,transparent);
29
+ --uupm-wash-18:color-mix(in srgb,var(--uupm-accent) 18%,transparent);
30
+ /* semantic */
31
+ --surface:var(--uupm-bg-1); --ink:var(--uupm-ink-0); --ink-muted:var(--uupm-ink-1);
32
+ --accent:var(--uupm-accent); --edge-live:var(--uupm-live); --edge-cold:var(--uupm-cold);
33
+ --federal:var(--uupm-indigo);
34
+ /* component */
35
+ --card-bg:var(--surface); --card-line:var(--n-card-line,#5c6680); --card-line-hover:var(--uupm-ink-1);
36
+ --toolbar-bg:color-mix(in srgb,var(--uupm-bg-1) 92%,transparent);
37
+ --grid-dot:color-mix(in srgb,var(--uupm-ink-1) 22%,transparent);
38
+ --scrim:color-mix(in srgb,var(--uupm-bg-0) 60%,transparent);
39
+ --error-ink:var(--n-error-ink,#fecaca); --error-panel:var(--n-error-panel,#2a0d10f2);
40
+ }
41
+
42
+ #dsh-plan{
43
+ position:absolute; inset:0; overflow:hidden; background:var(--uupm-bg-0); color:var(--ink);
44
+ font-size:13px; line-height:1.5; outline:none;
45
+ }
46
+ #dsh-plan .mono{font-family:ui-monospace,Cascadia,Consolas,monospace}
47
+ #dsh-plan:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
48
+
49
+ /* ── dot-grid world layer: screen-fixed, positioned by JS from the viewport ── */
50
+ #dsh-plan .p-grid{position:absolute; inset:0; z-index:0; pointer-events:none;
51
+ background-image:radial-gradient(circle, var(--grid-dot) 1px, transparent 1px);
52
+ background-size:24px 24px; background-position:0 0}
53
+
54
+ /* ── transformed world container ── */
55
+ #dsh-plan .p-world{position:absolute; inset:0; z-index:1; transform-origin:0 0}
56
+ #dsh-plan .p-layer{position:absolute; inset:0; pointer-events:none}
57
+ #dsh-plan .p-edges{position:absolute; overflow:visible; width:100%; height:100%; pointer-events:none}
58
+ /* The team frame is a grouping surface, not an interactive one: left
59
+ event-transparent so real drags reach the member cards inside it (the
60
+ frame head opts back in for group-drag/menu/Delete). */
61
+ #dsh-plan .p-frame{pointer-events:none}
62
+ #dsh-plan .p-frame-head{pointer-events:auto}
63
+ #dsh-plan .p-node{pointer-events:auto}
64
+ #dsh-plan .e-member{stroke:var(--uupm-edge-member); stroke-opacity:.38; stroke-width:1.5}
65
+ #dsh-plan .e-member.dashed{stroke-dasharray:3 4}
66
+
67
+ /* ── team frame ── */
68
+ /* --frame-line is the team hue set inline per frame (frameHue); --frame-ink is
69
+ what the frame actually paints with — the hue itself on dark, shaded 72%
70
+ toward black on light, where amber/lime/teal outlines fell to 1.8-2.2:1. */
71
+ #dsh-plan .p-frame{--frame-ink:var(--frame-line,#8B5CF6)}
72
+ @media (prefers-color-scheme: light){
73
+ #dsh-plan .p-frame{--frame-ink:color-mix(in srgb,var(--frame-line,#8B5CF6) 72%,black)}
74
+ }
75
+ #dsh-plan .p-frame{position:absolute; border:1px solid var(--frame-ink); border-radius:10px;
76
+ background:var(--frame-bg,var(--uupm-wash-5)); min-width:120px; min-height:60px}
77
+ #dsh-plan .p-frame-head{position:absolute; top:-13px; left:50%; transform:translateX(-50%);
78
+ height:24px; padding:0 10px; border-radius:6px; display:flex; gap:8px; align-items:center;
79
+ background:var(--uupm-bg-2); border:1px solid var(--frame-ink);
80
+ font-size:12px; font-weight:600; letter-spacing:.02em; white-space:nowrap; cursor:grab}
81
+ #dsh-plan .p-frame-head:active{cursor:grabbing}
82
+ #dsh-plan .p-frame-head:hover{border-color:var(--card-line-hover)}
83
+ #dsh-plan .p-frame-head .cnt{color:var(--ink-muted); font-weight:400}
84
+ #dsh-plan .p-frame-head .route{font-size:10.5px; color:var(--ink-muted)}
85
+ #dsh-plan .p-frame.is-mirror{border-style:dashed}
86
+ #dsh-plan .p-frame.is-roster{border-style:double;border-width:3px}
87
+ #dsh-plan .p-frame.is-roster .p-frame-head{border-style:dashed}
88
+ #dsh-plan .p-frame.is-syncing .p-frame-head .route{color:var(--accent)}
89
+ #dsh-plan .p-frame.is-unreachable{opacity:.76;border-style:dashed}
90
+ #dsh-plan .p-frame-unreachable{position:absolute;left:8px;top:20px;max-width:calc(100% - 16px);color:var(--uupm-error);font-size:11px;pointer-events:none}
91
+ #dsh-plan .p-node.remote.is-unreachable{opacity:.72;border-left-color:var(--uupm-error)}
92
+ #dsh-plan .p-node.remote.is-unreachable .sub{color:var(--uupm-error)}
93
+
94
+ /* ── node card (fixed 56px border-box height = world.NODE_H) ── */
95
+ #dsh-plan .p-node{position:absolute; width:172px; height:56px; padding:8px 26px 8px 14px;
96
+ background:var(--card-bg); border:1px solid var(--card-line); border-left:3px solid var(--edge-live);
97
+ border-radius:8px; cursor:grab; user-select:none;
98
+ transition:border-color 120ms cubic-bezier(0.2,0,0,1), transform 120ms cubic-bezier(0.2,0,0,1)}
99
+ #dsh-plan .p-node:hover{border-color:var(--card-line-hover)}
100
+ #dsh-plan .p-node:active{cursor:grabbing}
101
+ #dsh-plan .p-node:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
102
+ #dsh-plan .p-node.selected{outline:2px solid var(--accent); outline-offset:1px}
103
+ #dsh-plan .p-node.dragging{transform:scale(1.02); border-color:var(--accent)}
104
+ #dsh-plan .p-node.cold{border-left-color:var(--edge-cold)}
105
+ #dsh-plan .p-node .nm{font-weight:600; display:flex; align-items:center; gap:7px;
106
+ overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
107
+ /* The name span clips with an ellipsis (min-width:0 lets it shrink inside
108
+ the flex row) — without it a long peer address was hard-cut mid-digit.
109
+ 属主令 W2: TWO-LINE clamp for the name (long Chinese team titles read
110
+ fully); the card's 172x56 geometry is untouched — the clamp absorbs the
111
+ overflow. The handle line (.sub) stays single-line ellipsis below. */
112
+ #dsh-plan .p-node .nm-text{min-width:0; overflow:hidden; white-space:normal;
113
+ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; line-height:1.25}
114
+ /* A peer address has no break point, so the two-line clamp clipped it to one
115
+ line under the badge; let it wrap anywhere onto the second line. */
116
+ #dsh-plan .p-node.remote .nm-text{overflow-wrap:anywhere}
117
+ #dsh-plan .p-node .dot{width:8px; height:8px; border-radius:50%; background:var(--edge-live); flex:none}
118
+ #dsh-plan .p-node.cold .dot{background:transparent; border:1.5px solid var(--edge-cold)}
119
+ #dsh-plan .p-node .sub-row{display:flex; align-items:baseline; gap:6px; margin-top:2px; min-width:0}
120
+ #dsh-plan .p-node .sub-row .sub{flex:1 1 auto; min-width:0; margin-top:0}
121
+ #dsh-plan .p-node .work{flex:none; font-size:10.5px; font-weight:500; color:var(--ink-muted); white-space:nowrap}
122
+ #dsh-plan .p-node .work:empty{display:none}
123
+ #dsh-plan .p-node.is-working .work{color:var(--edge-live)}
124
+ #dsh-plan .p-node.is-working .dot{animation:p-working 1.4s ease-in-out infinite}
125
+ @keyframes p-working{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--edge-live) 55%,transparent)}50%{box-shadow:0 0 0 4px transparent}}
126
+ @media (prefers-reduced-motion: reduce){#dsh-plan .p-node.is-working .dot{animation:none}}
127
+ #dsh-plan .p-node .sub{font-size:11px; color:var(--ink-muted); margin-top:2px;
128
+ overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
129
+ /* W1: a picked search hit pulses its card for 1.2s (class removed on timer). */
130
+ #dsh-plan .p-node.pulse{box-shadow:0 0 0 2px var(--accent), 0 0 14px 4px var(--uupm-wash-15); border-color:var(--accent)}
131
+ /* W1: toolbar search box + hit list. */
132
+ #dsh-plan .p-search{position:relative}
133
+ #dsh-plan .p-search-input{width:170px; min-height:28px; padding:2px 8px; font-size:12px;
134
+ background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px}
135
+ #dsh-plan .p-search-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
136
+ #dsh-plan .p-search-list{position:absolute; top:100%; left:0; margin-top:4px; display:none;
137
+ min-width:240px; max-height:220px; overflow:auto; z-index:9;
138
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:6px}
139
+ #dsh-plan .p-search-hit{padding:5px 10px; font-size:12px; color:var(--ink); cursor:pointer; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
140
+ #dsh-plan .p-search-hit:hover, #dsh-plan .p-search-hit.active{background:var(--uupm-wash-15)}
141
+ #dsh-plan .p-node .prio{position:absolute; top:6px; right:8px; font-size:10px; color:var(--ink-muted);
142
+ border:1px solid var(--card-line); border-radius:4px; padding:0 5px}
143
+
144
+ /* ── marquee ── */
145
+ #dsh-plan .p-marquee{position:absolute; z-index:2; display:none; pointer-events:none;
146
+ border:1px solid var(--accent); background:var(--uupm-wash-8)}
147
+
148
+ /* ── HUD ── */
149
+ #dsh-plan .p-toolbar{position:absolute; top:16px; left:16px; z-index:3; display:flex; gap:6px; align-items:center;
150
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:6px 8px}
151
+ #dsh-plan .p-tabs{position:absolute; top:16px; left:50%; transform:translateX(-50%); z-index:3; display:flex; gap:4px;
152
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px}
153
+ #dsh-plan .p-tabs [role=tab]{background:transparent; color:var(--ink-muted); border:1px solid transparent; border-radius:6px;
154
+ padding:4px 12px; font:inherit; cursor:pointer}
155
+ #dsh-plan .p-tabs [role=tab]:hover{color:var(--ink); border-color:var(--card-line)}
156
+ #dsh-plan .p-tabs [role=tab][aria-selected=true]{color:var(--ink); border-color:var(--accent); background:var(--uupm-wash-12)}
157
+ #dsh-plan .p-tabs [role=tab]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
158
+ #dsh-plan .p-toolbar button{background:transparent; color:var(--ink); border:1px solid var(--card-line); border-radius:6px;
159
+ padding:4px 10px; font:inherit; cursor:pointer; white-space:nowrap}
160
+ #dsh-plan .p-toolbar button:hover{border-color:var(--accent)}
161
+ #dsh-plan .p-toolbar button:active{background:var(--uupm-wash-15)}
162
+ #dsh-plan .p-toolbar button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
163
+ #dsh-plan .p-zoom{min-width:44px; text-align:center; font-size:11px; color:var(--ink-muted)}
164
+ /* Toolbar groups (view | arrange | network): a hairline between them. */
165
+ #dsh-plan .p-sep{align-self:stretch; width:1px; margin:2px 4px; background:var(--card-line)}
166
+ #dsh-plan .p-lamp{display:flex; align-items:center; gap:6px; font-size:11px; color:var(--ink-muted); padding:0 6px}
167
+ #dsh-plan .p-lamp i{width:7px; height:7px; border-radius:50%; background:var(--ink-muted); transition:background 140ms ease}
168
+ #dsh-plan .p-lamp.saved i{background:var(--edge-live)}
169
+ #dsh-plan .p-lamp.pending i{background:var(--uupm-warn)}
170
+ #dsh-plan .p-lamp.error{color:var(--uupm-error); cursor:pointer}
171
+ #dsh-plan .p-lamp.error i{background:var(--uupm-error)}
172
+ #dsh-plan .p-lamp.error:hover{border:1px solid var(--uupm-error); border-radius:4px}
173
+
174
+ #dsh-plan .p-status{position:absolute; top:16px; right:16px; z-index:3; display:flex; gap:14px;
175
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:8px 14px; font-size:12px}
176
+ #dsh-plan .p-status b{font-weight:600}
177
+ #dsh-plan .ok{color:var(--edge-live)} #dsh-plan .warn{color:var(--uupm-warn)} #dsh-plan .mut{color:var(--ink-muted)}
178
+
179
+ #dsh-plan .p-legend{position:absolute; bottom:16px; left:16px; z-index:3; font-size:11px; color:var(--ink-muted);
180
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; padding:4px 10px}
181
+ #dsh-plan .p-legend summary{cursor:pointer; padding:2px 0; user-select:none}
182
+ #dsh-plan .p-legend summary:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
183
+ #dsh-plan .p-legend[open]{padding-bottom:8px}
184
+ #dsh-plan .p-legend-rows{display:grid; gap:4px; margin-top:4px}
185
+ #dsh-plan .p-legend i{display:inline-block; width:18px; border-top:2px solid var(--uupm-edge-member);
186
+ vertical-align:middle; margin-right:6px}
187
+ #dsh-plan .p-legend i.dashed{border-top-style:dashed}
188
+
189
+ /* ── narrow board (<680px): the HUD stacks instead of colliding ──
190
+ The toolbar, status chip and legend are absolutely placed for a wide
191
+ board; on a phone they overlapped each other and the toolbar's CJK
192
+ labels wrapped one glyph per line. The one-row sideways scroll that
193
+ followed hid the search box and half the actions off-screen (review
194
+ 2026-10-27), so the toolbar and status chip now flow at the top of the
195
+ board instead: the toolbar wraps, search first on its own row, and the
196
+ status chip sits under whatever height that takes. The legend (reference,
197
+ not state) hides. */
198
+ @media (max-width:680px){
199
+ #dsh-plan .p-toolbar{position:relative; top:auto; left:auto; margin:8px 8px 0; flex-wrap:wrap; row-gap:6px}
200
+ #dsh-plan .p-toolbar > *{flex:none}
201
+ #dsh-plan .p-toolbar .p-search{order:-1; flex:1 1 100%}
202
+ #dsh-plan .p-toolbar .p-sep{display:none}
203
+ #dsh-plan .p-toolbar .p-search-input{width:100%; box-sizing:border-box}
204
+ #dsh-plan .p-status{position:relative; top:auto; right:auto; left:auto; width:fit-content; margin:6px 8px 0; padding:6px 10px; gap:10px; flex-wrap:wrap}
205
+ #dsh-plan .p-legend{display:none}
206
+ }
207
+ @media (pointer:coarse){
208
+ #dsh-plan .p-toolbar button{min-height:36px; min-width:36px}
209
+ }
210
+
211
+ /* ── reduced motion: zero animation, state carried by text (hard baseline) ── */
212
+ @media (prefers-reduced-motion: reduce){
213
+ #dsh-plan *{transition:none !important; animation:none !important}
214
+ }
215
+
216
+ /* ── PR C: drop-target highlight, context menu, name dialog, notices ── */
217
+ #dsh-plan .p-frame.drop-target{border-color:var(--accent); border-width:2px; background:var(--uupm-wash-12)}
218
+ #dsh-plan .p-frame-head:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
219
+
220
+ /* ── PR D: federation overlay — peer badges, activity/federal edges, status fly ── */
221
+ /* Peer cards: the 'peer' tag is ~36px wide against the 26px gutter a P0–P9
222
+ tag fits in, so it sat on top of the address text — widen the gutter. */
223
+ #dsh-plan .p-node.remote{border-left-color:var(--federal); padding-right:46px}
224
+ #dsh-plan .p-node.remote .dot{background:var(--federal)}
225
+ #dsh-plan .p-node.remote .prio{color:var(--ink)}
226
+ /* gh#20 W-A link badge: linked green / stranger gray (fleet reserved);
227
+ the ✓/✗ verified mark rides inside the same chip. */
228
+ #dsh-plan .p-node .prio.lnk-linked{color:var(--live, #2e9e5b); border-color:var(--live, #2e9e5b)}
229
+ #dsh-plan .p-node .prio.lnk-stranger{color:var(--ink-muted); border-color:var(--card-line)}
230
+ #dsh-plan .e-activity{stroke:var(--accent); stroke-width:2; stroke-dasharray:8 6; stroke-opacity:.9}
231
+ #dsh-plan .e-federal{stroke:var(--federal); stroke-width:1.5; stroke-dasharray:6 5; stroke-opacity:.5}
232
+ #dsh-plan .edge-label{font-size:10.5px; fill:var(--ink-muted)}
233
+ /* in-flight routes flow only when motion is welcome; reduced-motion keeps
234
+ the zero-animation baseline (state stays in the label text). */
235
+ @media (prefers-reduced-motion: no-preference){
236
+ #dsh-plan .e-activity{animation:p-activity-flow 1.1s linear infinite}
237
+ }
238
+ @keyframes p-activity-flow{to{stroke-dashoffset:-28}}
239
+ #dsh-plan .p-status{cursor:pointer}
240
+ #dsh-plan .p-status:hover{border-color:var(--accent)}
241
+ #dsh-plan .p-status:active{background:var(--uupm-wash-12)}
242
+ #dsh-plan .p-status:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
243
+
244
+ #dsh-plan .p-menu{position:fixed; z-index:5; min-width:170px; display:grid; padding:4px; gap:2px;
245
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px}
246
+ #dsh-plan .p-menu [role=menuitem]{background:transparent; color:var(--ink); border:1px solid transparent;
247
+ border-radius:6px; padding:6px 10px; font:inherit; text-align:left; cursor:pointer; white-space:nowrap}
248
+ #dsh-plan .p-menu [role=menuitem]:hover{border-color:var(--card-line); background:var(--uupm-wash-8)}
249
+ #dsh-plan .p-menu [role=menuitem]:active{background:var(--uupm-wash-18)}
250
+ #dsh-plan .p-menu [role=menuitem]:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
251
+ #dsh-plan .p-menu [role=menuitem][aria-disabled=true]{color:var(--ink-muted); cursor:not-allowed}
252
+
253
+ #dsh-plan .p-dialog{position:absolute; inset:0; z-index:6; display:grid; place-items:center;
254
+ background:var(--scrim)}
255
+ #dsh-plan .p-dialog-panel{min-width:300px; padding:14px 16px; display:grid; gap:10px;
256
+ background:var(--uupm-bg-2); border:1px solid var(--card-line); border-radius:10px}
257
+ #dsh-plan .p-dialog-title{font-weight:600; font-size:12.5px; color:var(--ink)}
258
+ #dsh-plan .p-dialog-input{font:inherit; color:var(--ink); background:var(--uupm-bg-0);
259
+ border:1px solid var(--card-line); border-radius:6px; padding:7px 10px}
260
+ #dsh-plan .p-dialog-input:hover{border-color:color-mix(in srgb,var(--uupm-ink-1) 55%,transparent)}
261
+ #dsh-plan .p-dialog-input:focus-visible{outline:2px solid var(--accent); outline-offset:1px; border-color:var(--accent)}
262
+ #dsh-plan .p-dialog-err{min-height:1.2em; font-size:11px; color:var(--uupm-error)}
263
+ #dsh-plan .p-dialog-kind{display:grid; grid-template-columns:1fr 1fr; gap:4px; padding:3px;
264
+ background:var(--uupm-bg-0); border:1px solid var(--card-line); border-radius:8px}
265
+ #dsh-plan .p-dialog-kind button{font:inherit; color:var(--ink-muted); background:transparent; border:1px solid transparent;
266
+ border-radius:6px; padding:6px 10px; cursor:pointer}
267
+ #dsh-plan .p-dialog-kind button:hover{color:var(--ink); background:var(--uupm-wash-8)}
268
+ #dsh-plan .p-dialog-kind button[aria-checked=true]{color:var(--ink); font-weight:600; background:var(--uupm-wash-15);
269
+ border-color:var(--accent)}
270
+ #dsh-plan .p-dialog-kind button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
271
+ #dsh-plan .p-dialog-hint{font-size:11px; color:var(--ink-muted); max-width:340px}
272
+ #dsh-plan .p-dialog-row{display:flex; gap:8px; justify-content:flex-end}
273
+ #dsh-plan .p-dialog-row button{background:transparent; color:var(--ink); border:1px solid var(--card-line);
274
+ border-radius:6px; padding:5px 12px; font:inherit; cursor:pointer}
275
+ #dsh-plan .p-dialog-row button:hover{border-color:var(--accent)}
276
+ #dsh-plan .p-dialog-row button:active{background:var(--uupm-wash-15)}
277
+ #dsh-plan .p-dialog-row button:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
278
+
279
+ #dsh-plan .p-notice-stack{position:absolute; bottom:52px; left:50%; transform:translateX(-50%); z-index:5;
280
+ display:grid; gap:6px; justify-items:center; pointer-events:none}
281
+ #dsh-plan .p-notice{max-width:60ch; padding:7px 12px; font-size:12px; color:var(--ink);
282
+ background:var(--toolbar-bg); border:1px solid var(--card-line); border-radius:8px; white-space:nowrap;
283
+ overflow:hidden; text-overflow:ellipsis}
284
+ #dsh-plan .p-notice.error{color:var(--error-ink); border-color:var(--uupm-error); background:var(--error-panel)}
285
+
286
+ /* Panel-slim migration: the unjoined-sessions join affordance on the toolbar. */
287
+ #dsh-plan .p-netbtn{color:var(--ink-muted)}
288
+ #dsh-plan .p-netbtn:not([disabled]){color:var(--ink)}
289
+ #dsh-plan .p-netmenu{max-height:240px; overflow:auto}
290
+
291
+ /* ── Node list drawer (network-membership-display): teamed nodes hold the
292
+ canvas; every other session is managed here, switched by host. ── */
293
+ #dsh-plan .p-nodebtn{color:var(--ink)}
294
+ #dsh-plan .p-nodelist{min-width:300px; max-width:340px; max-height:420px; overflow:auto; padding:6px}
295
+ #dsh-plan .p-nodelist-head{display:flex; align-items:center; justify-content:space-between; gap:8px;
296
+ padding:2px 4px 6px; border-bottom:1px solid var(--card-line)}
297
+ #dsh-plan .p-nodelist-title{font-size:12px; font-weight:600; color:var(--ink)}
298
+ #dsh-plan .p-nodesel{font:inherit; font-size:11.5px; color:var(--ink); background:var(--uupm-bg-0);
299
+ border:1px solid var(--card-line); border-radius:6px; padding:2px 6px; max-width:220px}
300
+ #dsh-plan .p-nodesel:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
301
+ #dsh-plan .p-nodelist-group{font-size:11px; color:var(--ink-muted); padding:8px 4px 3px;
302
+ display:flex; align-items:baseline; gap:8px}
303
+ #dsh-plan .p-nodelist-note{font-size:10px; color:var(--ink-muted); opacity:.8}
304
+ #dsh-plan .p-noderow{display:flex; align-items:center; gap:6px; padding:3px 4px; border-radius:6px;
305
+ font-size:12px; color:var(--ink)}
306
+ #dsh-plan .p-noderow:hover{background:var(--uupm-wash-8)}
307
+ #dsh-plan .p-nodedot{flex:none; width:7px; height:7px; border-radius:50%; background:var(--edge-live)}
308
+ #dsh-plan .p-nodedot.cold{background:var(--ink-muted)}
309
+ #dsh-plan .p-nodedot.off{background:transparent; border:1px solid var(--ink-muted)}
310
+ #dsh-plan .p-nodename{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
311
+ #dsh-plan .p-nonet{flex:none; font-size:10px; color:var(--uupm-warn); border:1px solid var(--uupm-warn);
312
+ border-radius:4px; padding:0 4px}
313
+ #dsh-plan .p-teamcount{flex:none; font-size:10px; color:var(--ink-muted); border:1px solid var(--card-line);
314
+ border-radius:4px; padding:0 4px}
315
+ #dsh-plan .p-listbtn{flex:none; background:transparent; color:var(--ink); border:1px solid var(--card-line);
316
+ border-radius:5px; padding:1px 7px; font:inherit; font-size:11px; cursor:pointer}
317
+ #dsh-plan .p-listbtn:hover{border-color:var(--card-line-hover); background:var(--uupm-wash-8)}
318
+ #dsh-plan .p-listbtn:focus-visible{outline:2px solid var(--accent); outline-offset:1px}
319
+ #dsh-plan .p-nodelist-empty{padding:10px 4px; font-size:11.5px; color:var(--ink-muted)}
320
+
321
+ /* ── Frame resize handles (owner ruling: frames stretch/adjust) — eight
322
+ invisible hit zones that show the grip on hover. ── */
323
+ #dsh-plan .p-frame{position:absolute}
324
+ #dsh-plan .p-frame-handle{position:absolute; pointer-events:auto; width:10px; height:10px; z-index:2; opacity:0;
325
+ transition:opacity 120ms ease}
326
+ #dsh-plan .p-frame:hover .p-frame-handle{opacity:1}
327
+ #dsh-plan .p-frame-handle::after{content:''; position:absolute; inset:2px; background:var(--frame-ink);
328
+ border-radius:2px}
329
+ #dsh-plan .p-h-n{top:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
330
+ #dsh-plan .p-h-s{bottom:-5px; left:calc(50% - 12px); width:24px; height:8px; cursor:ns-resize}
331
+ #dsh-plan .p-h-e{right:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
332
+ #dsh-plan .p-h-w{left:-5px; top:calc(50% - 12px); width:8px; height:24px; cursor:ew-resize}
333
+ #dsh-plan .p-h-ne{top:-5px; right:-5px; cursor:nesw-resize}
334
+ #dsh-plan .p-h-nw{top:-5px; left:-5px; cursor:nwse-resize}
335
+ #dsh-plan .p-h-se{bottom:-5px; right:-5px; cursor:nwse-resize}
336
+ #dsh-plan .p-h-sw{bottom:-5px; left:-5px; cursor:nesw-resize}
337
+ #dsh-plan .p-h-ne::after, #dsh-plan .p-h-sw::after{transform:rotate(45deg)}
338
+ #dsh-plan .p-h-nw::after, #dsh-plan .p-h-se::after{transform:rotate(-45deg)}
339
+ #dsh-plan .p-frame.selected .p-frame-handle{opacity:1}
340
+ #dsh-plan .p-frame.selected{outline:1px solid var(--frame-ink)}