@1agh/maude 0.34.0 → 0.36.0
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/apps/studio/client/app.jsx +90 -3
- package/apps/studio/client/panels/GitPanel.jsx +6 -0
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +199 -68
- package/apps/studio/client/styles/3-shell-maude.css +22 -0
- package/apps/studio/dist/client.bundle.js +21 -53108
- package/apps/studio/dist/comment-mount.js +1 -2048
- package/apps/studio/dist/styles.css +1 -12220
- package/apps/studio/git/endpoints.ts +17 -0
- package/apps/studio/git/service.ts +483 -40
- package/apps/studio/github/endpoints.ts +28 -4
- package/apps/studio/github/identity-cache.ts +138 -0
- package/apps/studio/github/service.ts +40 -23
- package/apps/studio/http.ts +12 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +2 -0
- package/apps/studio/test/git-branches.test.ts +141 -3
- package/apps/studio/test/github-api.test.ts +58 -1
- package/apps/studio/test/identity-cache.test.ts +83 -0
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +89 -0
- package/apps/studio/whats-new.json +18 -0
- package/package.json +15 -10
- package/plugins/flow/.claude-plugin/config.schema.json +4 -4
|
@@ -449,10 +449,11 @@ const STICONS = {
|
|
|
449
449
|
<polyline points="3 12.8 3 13.6 13 13.6 13 12.8" />
|
|
450
450
|
</>
|
|
451
451
|
),
|
|
452
|
+
// lucide `rotate-cw`, scaled from the 24px source into our 16px viewBox.
|
|
452
453
|
reload: (
|
|
453
454
|
<>
|
|
454
|
-
<path d="
|
|
455
|
-
<
|
|
455
|
+
<path d="M14 8a6 6 0 1 1-2-4.47L14 5.33" />
|
|
456
|
+
<path d="M14 2v3.33h-3.33" />
|
|
456
457
|
</>
|
|
457
458
|
),
|
|
458
459
|
help: (
|
|
@@ -1092,10 +1093,23 @@ function FileRow({ file, activePath, onOpen, onDelete, openCount: oc, depth, kin
|
|
|
1092
1093
|
// Delete only real canvases in a deletable group (onDelete is undefined for the
|
|
1093
1094
|
// DS group + runtime files); the server enforces the rest.
|
|
1094
1095
|
const canDelete = isCanvas && typeof onDelete === 'function' && kind !== 'runtime';
|
|
1096
|
+
// Stable hook for the desktop E2E harness (data-testid convention — see the
|
|
1097
|
+
// `desktop-e2e` skill): canvas rows only, slug derived from the relative path
|
|
1098
|
+
// (e.g. `ui/Smoke.tsx` → `canvas-row-ui-smoke`).
|
|
1099
|
+
const testId = isCanvas
|
|
1100
|
+
? 'canvas-row-' +
|
|
1101
|
+
file.path
|
|
1102
|
+
.replace(/^\.[^/]+\//, '') // strip the leading designRoot dot-folder (.design/)
|
|
1103
|
+
.replace(CANVAS_EXT_RE, '')
|
|
1104
|
+
.replace(/[^a-z0-9]+/gi, '-')
|
|
1105
|
+
.toLowerCase()
|
|
1106
|
+
.replace(/^-+|-+$/g, '')
|
|
1107
|
+
: undefined;
|
|
1095
1108
|
const row = (
|
|
1096
1109
|
<button
|
|
1097
1110
|
type="button"
|
|
1098
1111
|
role="treeitem"
|
|
1112
|
+
data-testid={testId}
|
|
1099
1113
|
aria-selected={isSel}
|
|
1100
1114
|
aria-disabled={inert ? 'true' : undefined}
|
|
1101
1115
|
tabIndex={isSel ? 0 : -1}
|
|
@@ -1383,6 +1397,8 @@ function Sidebar({
|
|
|
1383
1397
|
onToggleSection,
|
|
1384
1398
|
onNewBoard,
|
|
1385
1399
|
onDeleteBoard,
|
|
1400
|
+
onRefresh,
|
|
1401
|
+
refreshing,
|
|
1386
1402
|
collapsed,
|
|
1387
1403
|
onCollapse,
|
|
1388
1404
|
width,
|
|
@@ -1457,6 +1473,19 @@ function Sidebar({
|
|
|
1457
1473
|
>
|
|
1458
1474
|
<StIcon name="plus" size={15} />
|
|
1459
1475
|
</button>
|
|
1476
|
+
{onRefresh && (
|
|
1477
|
+
<button
|
|
1478
|
+
type="button"
|
|
1479
|
+
className={'st-iconbtn st-refresh' + (refreshing ? ' is-spinning' : '')}
|
|
1480
|
+
data-tip="Refresh files · ⇧⌘R"
|
|
1481
|
+
aria-label="Refresh files"
|
|
1482
|
+
aria-busy={refreshing || undefined}
|
|
1483
|
+
disabled={refreshing}
|
|
1484
|
+
onClick={() => onRefresh()}
|
|
1485
|
+
>
|
|
1486
|
+
<StIcon name="reload" size={15} />
|
|
1487
|
+
</button>
|
|
1488
|
+
)}
|
|
1460
1489
|
<span
|
|
1461
1490
|
className="st-live"
|
|
1462
1491
|
data-tip={wsConnected ? 'live · file index synced' : 'reconnecting…'}
|
|
@@ -1553,7 +1582,7 @@ function Sidebar({
|
|
|
1553
1582
|
</div>
|
|
1554
1583
|
</div>
|
|
1555
1584
|
|
|
1556
|
-
<div className="st-tree" role="tree" aria-label="Project file tree">
|
|
1585
|
+
<div className="st-tree" role="tree" aria-label="Project file tree" data-testid="canvas-list">
|
|
1557
1586
|
{filteredGroups.map((g) => {
|
|
1558
1587
|
// Hide gitignored runtime / orphan-only project sections by default.
|
|
1559
1588
|
// Active search overrides — if the user typed a query, they want hits
|
|
@@ -2748,6 +2777,7 @@ function Viewport({
|
|
|
2748
2777
|
src={canvasUrl(t.path, cfg)}
|
|
2749
2778
|
className={t.path === activePath ? 'active' : ''}
|
|
2750
2779
|
data-path={t.path}
|
|
2780
|
+
data-testid={t.path === activePath ? 'canvas-frame' : undefined}
|
|
2751
2781
|
onLoad={() => onIframeLoad?.(t.path)}
|
|
2752
2782
|
// T2 (9.1-A) — only sandbox + delegate clipboard when the canvas is
|
|
2753
2783
|
// served cross-origin (canvasOrigin present = the split is on). In
|
|
@@ -3136,6 +3166,7 @@ function StatusBar({
|
|
|
3136
3166
|
(changesCount > 0 ? ' has-changes' : unpushed > 0 ? ' has-unpushed' : '')
|
|
3137
3167
|
}
|
|
3138
3168
|
onClick={onOpenChanges}
|
|
3169
|
+
data-testid="open-changes"
|
|
3139
3170
|
data-tip="Open Changes · ⌘⇧G"
|
|
3140
3171
|
data-tip-pos="top"
|
|
3141
3172
|
aria-label="Open Changes panel"
|
|
@@ -6303,6 +6334,50 @@ function App() {
|
|
|
6303
6334
|
|
|
6304
6335
|
const reloadTree = useCallback(() => loadTree(), [loadTree]);
|
|
6305
6336
|
|
|
6337
|
+
// User-facing tree refresh with a visible spin. The header button and ⌘⇧R call
|
|
6338
|
+
// this so the reload icon spins for at least one beat — even when /_index-data
|
|
6339
|
+
// returns instantly — so the action registers visually ("something is
|
|
6340
|
+
// happening"). The min-duration race keeps the spin from flashing for one
|
|
6341
|
+
// frame on a fast read; the ref guard ignores re-entrant clicks. The passive
|
|
6342
|
+
// focus backstop below uses the plain reloadTree (no icon to animate).
|
|
6343
|
+
const [treeRefreshing, setTreeRefreshing] = useState(false);
|
|
6344
|
+
const treeRefreshingRef = useRef(false);
|
|
6345
|
+
const refreshTree = useCallback(async () => {
|
|
6346
|
+
if (treeRefreshingRef.current) return;
|
|
6347
|
+
treeRefreshingRef.current = true;
|
|
6348
|
+
setTreeRefreshing(true);
|
|
6349
|
+
try {
|
|
6350
|
+
await Promise.all([loadTree(), new Promise((r) => setTimeout(r, 550))]);
|
|
6351
|
+
} finally {
|
|
6352
|
+
treeRefreshingRef.current = false;
|
|
6353
|
+
setTreeRefreshing(false);
|
|
6354
|
+
}
|
|
6355
|
+
}, [loadTree]);
|
|
6356
|
+
|
|
6357
|
+
// Backstop for the desktop sidecar: re-list the tree whenever the window
|
|
6358
|
+
// regains focus. The fs-watch → canvas-list-update auto-refresh can drop
|
|
6359
|
+
// events in a `bun --compile` standalone binary (recursive fs.watch is
|
|
6360
|
+
// unreliable there) and across a sidecar respawn / WS reconnect, leaving a
|
|
6361
|
+
// stale tree after a canvas was created from the ACP chat or a terminal.
|
|
6362
|
+
// `/_index-data` is a cheap read and people tab away to the agent and back, so
|
|
6363
|
+
// this turns "switch projects to force a refresh" into "just come back to the
|
|
6364
|
+
// window". Debounced so a rapid blur/focus burst coalesces to one re-read.
|
|
6365
|
+
useEffect(() => {
|
|
6366
|
+
let t = null;
|
|
6367
|
+
const onFocus = () => {
|
|
6368
|
+
if (t) clearTimeout(t);
|
|
6369
|
+
t = setTimeout(() => {
|
|
6370
|
+
t = null;
|
|
6371
|
+
reloadTree();
|
|
6372
|
+
}, 150);
|
|
6373
|
+
};
|
|
6374
|
+
window.addEventListener('focus', onFocus);
|
|
6375
|
+
return () => {
|
|
6376
|
+
window.removeEventListener('focus', onFocus);
|
|
6377
|
+
if (t) clearTimeout(t);
|
|
6378
|
+
};
|
|
6379
|
+
}, [reloadTree]);
|
|
6380
|
+
|
|
6306
6381
|
// Phase 22 — create a blank brief board from the tree header. POSTs to the
|
|
6307
6382
|
// main-origin-only /_api/canvas (the untrusted canvas iframe can't reach it),
|
|
6308
6383
|
// then refreshes the tree and opens the new board so it's immediately the
|
|
@@ -6784,6 +6859,15 @@ function App() {
|
|
|
6784
6859
|
setPaletteOpen((v) => !v);
|
|
6785
6860
|
return;
|
|
6786
6861
|
}
|
|
6862
|
+
// Cmd+Shift+R — refresh the FILES tree (re-read /_index-data). The fs-watch
|
|
6863
|
+
// → canvas-list-update auto-refresh can miss events in the compiled desktop
|
|
6864
|
+
// sidecar (recursive fs.watch is unreliable in a bun --compile binary), and
|
|
6865
|
+
// ⌘R is taken by canvas-iframe reload, so this is the manual escape hatch.
|
|
6866
|
+
if (meta && e.shiftKey && (e.key === 'r' || e.key === 'R')) {
|
|
6867
|
+
e.preventDefault();
|
|
6868
|
+
refreshTree();
|
|
6869
|
+
return;
|
|
6870
|
+
}
|
|
6787
6871
|
// Cmd+R — reload active iframe (override browser reload)
|
|
6788
6872
|
if (meta && (e.key === 'r' || e.key === 'R')) {
|
|
6789
6873
|
e.preventDefault();
|
|
@@ -6933,6 +7017,7 @@ function App() {
|
|
|
6933
7017
|
return () => window.removeEventListener('keydown', onKey);
|
|
6934
7018
|
}, [
|
|
6935
7019
|
reloadActive,
|
|
7020
|
+
refreshTree,
|
|
6936
7021
|
selected,
|
|
6937
7022
|
activePath,
|
|
6938
7023
|
focusedCommentId,
|
|
@@ -7216,6 +7301,8 @@ function App() {
|
|
|
7216
7301
|
onToggleSection={toggleSection}
|
|
7217
7302
|
onNewBoard={createBoard}
|
|
7218
7303
|
onDeleteBoard={deleteBoard}
|
|
7304
|
+
onRefresh={refreshTree}
|
|
7305
|
+
refreshing={treeRefreshing}
|
|
7219
7306
|
collapsed={!sidebarOpen}
|
|
7220
7307
|
onCollapse={() => setSidebarOpen(false)}
|
|
7221
7308
|
width={sbSize.w}
|
|
@@ -411,6 +411,7 @@ export default function GitPanel({
|
|
|
411
411
|
<button
|
|
412
412
|
type="button"
|
|
413
413
|
className="btn btn--primary gp-publish"
|
|
414
|
+
data-testid="git-publish"
|
|
414
415
|
data-tour="publish"
|
|
415
416
|
disabled={!!busy}
|
|
416
417
|
onClick={() =>
|
|
@@ -435,6 +436,7 @@ export default function GitPanel({
|
|
|
435
436
|
className={'st-rpanel gp-panel' + (resizing ? ' is-resizing' : '')}
|
|
436
437
|
style={width ? { width, flexBasis: width } : undefined}
|
|
437
438
|
aria-label="Changes"
|
|
439
|
+
data-testid="git-panel"
|
|
438
440
|
>
|
|
439
441
|
<div className="gp-head">
|
|
440
442
|
<div className="gp-panel-hd">
|
|
@@ -494,6 +496,7 @@ export default function GitPanel({
|
|
|
494
496
|
<button
|
|
495
497
|
type="button"
|
|
496
498
|
className="btn btn--sm"
|
|
499
|
+
data-testid="git-get-latest"
|
|
497
500
|
disabled={!!busy}
|
|
498
501
|
onClick={() =>
|
|
499
502
|
run('getLatest', onGetLatest, {
|
|
@@ -559,6 +562,7 @@ export default function GitPanel({
|
|
|
559
562
|
<button
|
|
560
563
|
type="button"
|
|
561
564
|
className="btn btn--ghost btn--sm"
|
|
565
|
+
data-testid="git-get-latest"
|
|
562
566
|
data-tour="pull"
|
|
563
567
|
disabled={!!busy}
|
|
564
568
|
onClick={() =>
|
|
@@ -639,6 +643,7 @@ export default function GitPanel({
|
|
|
639
643
|
</label>
|
|
640
644
|
<textarea
|
|
641
645
|
className="gp-msg"
|
|
646
|
+
data-testid="git-commit-message"
|
|
642
647
|
placeholder="Describe what changed in this version…"
|
|
643
648
|
aria-label="Describe what changed in this version"
|
|
644
649
|
rows={2}
|
|
@@ -665,6 +670,7 @@ export default function GitPanel({
|
|
|
665
670
|
<button
|
|
666
671
|
type="button"
|
|
667
672
|
className="btn btn--ghost btn--sm"
|
|
673
|
+
data-testid="git-save-all"
|
|
668
674
|
disabled={!message.trim() || !!busy}
|
|
669
675
|
title="Save every change"
|
|
670
676
|
onClick={async () => {
|