@njeybe/nativ 2.2.0 → 2.3.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/CHANGELOG.md +14 -1
- package/README.md +15 -0
- package/dist/server/studio-ui/document.d.ts +3 -0
- package/dist/server/studio-ui/document.js +22 -0
- package/dist/server/studio-ui/pages/benchmarks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/benchmarks/index.js +11 -0
- package/dist/server/studio-ui/pages/benchmarks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/script.js +104 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/benchmarks/styles.js +30 -0
- package/dist/server/studio-ui/pages/database/browser.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/browser.js +326 -0
- package/dist/server/studio-ui/pages/database/connection.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/connection.js +158 -0
- package/dist/server/studio-ui/pages/database/data-shell.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/data-shell.js +165 -0
- package/dist/server/studio-ui/pages/database/drift.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/drift.js +301 -0
- package/dist/server/studio-ui/pages/database/events.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/events.js +231 -0
- package/dist/server/studio-ui/pages/database/explorer.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/explorer.js +183 -0
- package/dist/server/studio-ui/pages/database/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/index.js +28 -0
- package/dist/server/studio-ui/pages/database/markup.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/markup.js +32 -0
- package/dist/server/studio-ui/pages/database/state.d.ts +2 -0
- package/dist/server/studio-ui/pages/database/state.js +57 -0
- package/dist/server/studio-ui/pages/database/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/database/styles.js +118 -0
- package/dist/server/studio-ui/pages/flow/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/flow/index.js +11 -0
- package/dist/server/studio-ui/pages/flow/layout.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/layout.js +105 -0
- package/dist/server/studio-ui/pages/flow/plan-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/plan-view.js +67 -0
- package/dist/server/studio-ui/pages/flow/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/script.js +61 -0
- package/dist/server/studio-ui/pages/flow/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/styles.js +59 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-model.js +95 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.d.ts +1 -0
- package/dist/server/studio-ui/pages/flow/timeline-view.js +92 -0
- package/dist/server/studio-ui/pages/home/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/home/index.js +11 -0
- package/dist/server/studio-ui/pages/home/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/script.js +160 -0
- package/dist/server/studio-ui/pages/home/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/home/styles.js +48 -0
- package/dist/server/studio-ui/pages/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/index.js +17 -0
- package/dist/server/studio-ui/pages/tasks/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/tasks/index.js +11 -0
- package/dist/server/studio-ui/pages/tasks/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/script.js +96 -0
- package/dist/server/studio-ui/pages/tasks/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/tasks/styles.js +37 -0
- package/dist/server/studio-ui/pages/team/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/team/index.js +11 -0
- package/dist/server/studio-ui/pages/team/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/script.js +112 -0
- package/dist/server/studio-ui/pages/team/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/team/styles.js +22 -0
- package/dist/server/studio-ui/pages/types.d.ts +13 -0
- package/dist/server/studio-ui/pages/types.js +1 -0
- package/dist/server/studio-ui/pages/worktrees/index.d.ts +2 -0
- package/dist/server/studio-ui/pages/worktrees/index.js +11 -0
- package/dist/server/studio-ui/pages/worktrees/script.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/script.js +104 -0
- package/dist/server/studio-ui/pages/worktrees/styles.d.ts +1 -0
- package/dist/server/studio-ui/pages/worktrees/styles.js +15 -0
- package/dist/server/studio-ui/shell/actions.d.ts +1 -0
- package/dist/server/studio-ui/shell/actions.js +49 -0
- package/dist/server/studio-ui/shell/boot.d.ts +2 -0
- package/dist/server/studio-ui/shell/boot.js +13 -0
- package/dist/server/studio-ui/shell/console.d.ts +1 -0
- package/dist/server/studio-ui/shell/console.js +194 -0
- package/dist/server/studio-ui/shell/core.d.ts +1 -0
- package/dist/server/studio-ui/shell/core.js +50 -0
- package/dist/server/studio-ui/shell/detail-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail-view.js +80 -0
- package/dist/server/studio-ui/shell/detail.d.ts +1 -0
- package/dist/server/studio-ui/shell/detail.js +83 -0
- package/dist/server/studio-ui/shell/dispatch.d.ts +1 -0
- package/dist/server/studio-ui/shell/dispatch.js +156 -0
- package/dist/server/studio-ui/shell/events-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/events-tools.js +94 -0
- package/dist/server/studio-ui/shell/format.d.ts +1 -0
- package/dist/server/studio-ui/shell/format.js +151 -0
- package/dist/server/studio-ui/shell/icons.d.ts +2 -0
- package/dist/server/studio-ui/shell/icons.js +20 -0
- package/dist/server/studio-ui/shell/markup-head.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-head.js +43 -0
- package/dist/server/studio-ui/shell/markup-panels.d.ts +2 -0
- package/dist/server/studio-ui/shell/markup-panels.js +7 -0
- package/dist/server/studio-ui/shell/markup-tools.d.ts +1 -0
- package/dist/server/studio-ui/shell/markup-tools.js +222 -0
- package/dist/server/studio-ui/shell/model.d.ts +1 -0
- package/dist/server/studio-ui/shell/model.js +192 -0
- package/dist/server/studio-ui/shell/paint.d.ts +1 -0
- package/dist/server/studio-ui/shell/paint.js +154 -0
- package/dist/server/studio-ui/shell/palette-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette-view.js +60 -0
- package/dist/server/studio-ui/shell/palette.d.ts +1 -0
- package/dist/server/studio-ui/shell/palette.js +80 -0
- package/dist/server/studio-ui/shell/pipeline-state.d.ts +1 -0
- package/dist/server/studio-ui/shell/pipeline-state.js +152 -0
- package/dist/server/studio-ui/shell/proposals.d.ts +1 -0
- package/dist/server/studio-ui/shell/proposals.js +197 -0
- package/dist/server/studio-ui/shell/pure.d.ts +3 -0
- package/dist/server/studio-ui/shell/pure.js +9 -0
- package/dist/server/studio-ui/shell/router.d.ts +1 -0
- package/dist/server/studio-ui/shell/router.js +95 -0
- package/dist/server/studio-ui/shell/sidebar-view.d.ts +1 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +38 -0
- package/dist/server/studio-ui/shell/sync.d.ts +1 -0
- package/dist/server/studio-ui/shell/sync.js +120 -0
- package/dist/server/studio-ui/shell/toasts-format.d.ts +1 -0
- package/dist/server/studio-ui/shell/toasts-format.js +89 -0
- package/dist/server/studio-ui/shell/triage.d.ts +1 -0
- package/dist/server/studio-ui/shell/triage.js +210 -0
- package/dist/server/studio-ui/styles/console.d.ts +1 -0
- package/dist/server/studio-ui/styles/console.js +83 -0
- package/dist/server/studio-ui/styles/dialogs.d.ts +1 -0
- package/dist/server/studio-ui/styles/dialogs.js +118 -0
- package/dist/server/studio-ui/styles/responsive.d.ts +1 -0
- package/dist/server/studio-ui/styles/responsive.js +28 -0
- package/dist/server/studio-ui/styles/shell.d.ts +1 -0
- package/dist/server/studio-ui/styles/shell.js +117 -0
- package/dist/server/studio-ui/styles/strategist.d.ts +1 -0
- package/dist/server/studio-ui/styles/strategist.js +77 -0
- package/dist/server/studio-ui/styles/tokens-base.d.ts +1 -0
- package/dist/server/studio-ui/styles/tokens-base.js +131 -0
- package/dist/server/studio-ui.d.ts +1 -10
- package/dist/server/studio-ui.js +63 -5314
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Database Studio view styles.
|
|
2
|
+
export const databaseCss = String.raw `/* Database Studio */
|
|
3
|
+
.telemetry { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 4px; }
|
|
4
|
+
.pill { display: inline-flex; align-items: center; gap: 8px; max-width: 100%; padding: 4px 10px; font-size: 13px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-control); box-shadow: var(--shadow); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
5
|
+
.pill b { font-weight: 600; }
|
|
6
|
+
.diagnostic-banner { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 12px 16px; margin: 12px 0 16px; background: var(--danger-bg); border: 1px solid var(--danger-line); border-radius: var(--r-control); }
|
|
7
|
+
.diagnostic-content { display: flex; align-items: center; gap: 10px; font-size: 13.5px; }
|
|
8
|
+
.diagnostic-tag { font-family: var(--mono); font-size: 11px; font-weight: 700; color: var(--danger); background: var(--surface); border: 1px solid var(--danger-line); padding: 0 7px; border-radius: var(--r-control); }
|
|
9
|
+
.diagnostic-msg code { background: var(--surface); padding: 1px 6px; border-radius: 4px; font-family: var(--mono); }
|
|
10
|
+
.diagnostic-actions { display: flex; gap: 8px; }
|
|
11
|
+
.template-helper-box { background: var(--active-bg); border: 1px solid var(--active-line); border-radius: var(--r-control); padding: 12px 14px; margin-bottom: 16px; }
|
|
12
|
+
.template-helper-label { font-size: 12.5px; color: var(--muted); margin-bottom: 8px; }
|
|
13
|
+
.template-helper-label code { color: var(--active); font-weight: 600; }
|
|
14
|
+
.template-chips { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
15
|
+
.template-chip { font-family: var(--mono); font-size: 12.5px; }
|
|
16
|
+
.template-chip:hover { border-color: var(--active); }
|
|
17
|
+
.chip-dot { width: 6px; height: 6px; border-radius: 50%; display: inline-block; }
|
|
18
|
+
.chip-dot.on { background: var(--success); }
|
|
19
|
+
.chip-dot.off { background: var(--danger); }
|
|
20
|
+
|
|
21
|
+
.thumb-pop { position: fixed; z-index: 60; width: 220px; padding: 8px; border-radius: var(--r-card); border: 1px solid var(--border); background: var(--surface); box-shadow: var(--shadow-float); pointer-events: none; }
|
|
22
|
+
.thumb-box { display: grid; place-items: center; min-height: 120px; max-height: 200px; overflow: hidden; border-radius: var(--r-control); background: repeating-conic-gradient(var(--chip) 0% 25%, var(--surface) 0% 50%) 50% / 16px 16px; }
|
|
23
|
+
.thumb-box img { max-width: 100%; max-height: 200px; display: block; }
|
|
24
|
+
.thumb-box .ph { color: var(--muted); font-size: 12px; text-align: center; padding: 12px; }
|
|
25
|
+
.thumb-meta { margin-top: 6px; font-size: 11.5px; color: var(--muted); overflow-wrap: anywhere; }
|
|
26
|
+
.media-modal .thumb-box { min-height: 220px; max-height: 360px; }
|
|
27
|
+
.media-modal .thumb-box img { max-height: 360px; }
|
|
28
|
+
.media-modal dl { display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px; margin: 14px 0 0; font-size: 13px; }
|
|
29
|
+
.media-modal dt { color: var(--muted); }
|
|
30
|
+
.media-modal dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
|
|
31
|
+
.media-note { margin-top: 12px; font-size: 13px; line-height: 1.5; color: var(--muted); }
|
|
32
|
+
|
|
33
|
+
.toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 14px; }
|
|
34
|
+
.input { min-height: 32px; padding: 6px 10px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-control); min-width: 0; }
|
|
35
|
+
.input:focus { border-color: var(--primary); outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 15%, transparent); }
|
|
36
|
+
textarea.input { width: 100%; resize: vertical; line-height: 1.5; }
|
|
37
|
+
.search { flex: 1; min-width: 180px; max-width: 420px; }
|
|
38
|
+
.panes { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
39
|
+
.panes-full { grid-template-columns: 1fr; }
|
|
40
|
+
.pane { padding: 16px; min-width: 0; }
|
|
41
|
+
.pane-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 12px; }
|
|
42
|
+
.pane-head h2 { margin: 0; font-size: 15.5px; display: flex; align-items: center; gap: 8px; white-space: nowrap; flex: none; }
|
|
43
|
+
.pane-head .meta { color: var(--muted); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
44
|
+
.table-card { border: 1px solid var(--border); border-radius: var(--r-control); margin-bottom: 8px; background: var(--surface); }
|
|
45
|
+
.table-card > summary { list-style: none; cursor: pointer; padding: 10px 12px; display: flex; align-items: center; gap: 8px; border-radius: var(--r-control); }
|
|
46
|
+
.table-card > summary::-webkit-details-marker { display: none; }
|
|
47
|
+
.table-card > summary:hover { background: var(--bg); }
|
|
48
|
+
.table-card > summary::before { content: "\25B8"; color: var(--muted); transition: transform .15s; }
|
|
49
|
+
.table-card[open] > summary::before { transform: rotate(90deg); }
|
|
50
|
+
.table-card .tname { font-family: var(--mono); font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
|
|
51
|
+
.table-card .ccount { margin-left: auto; color: var(--muted); font-size: 12.5px; white-space: nowrap; }
|
|
52
|
+
.table-body { padding: 0 12px 12px; overflow-x: auto; }
|
|
53
|
+
.desc { color: var(--muted); margin: 0 0 8px; font-size: 13.5px; line-height: 1.5; }
|
|
54
|
+
table.cols { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
55
|
+
table.cols th { text-align: left; color: var(--muted); font-weight: 500; font-size: 12.5px; padding: 6px 8px; border-bottom: 1px solid var(--border); white-space: nowrap; }
|
|
56
|
+
table.cols td { padding: 6px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
57
|
+
table.cols td.mono { white-space: nowrap; }
|
|
58
|
+
.subhead { font-size: 12.5px; color: var(--muted); margin: 12px 0 6px; text-transform: uppercase; letter-spacing: .05em; }
|
|
59
|
+
.idx-list { margin: 0; padding-left: 18px; font-size: 13px; }
|
|
60
|
+
|
|
61
|
+
.summary-banner { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; padding: 16px; margin-bottom: 16px; }
|
|
62
|
+
.stat { padding: 4px 8px; }
|
|
63
|
+
.stat .n { font-size: 26px; font-weight: 650; line-height: 1.1; font-variant-numeric: tabular-nums; }
|
|
64
|
+
.stat .l { color: var(--muted); font-size: 12.5px; }
|
|
65
|
+
.risk-HIGH { color: var(--danger); } .risk-MEDIUM { color: var(--active); } .risk-LOW { color: var(--success); } .risk-NONE { color: var(--muted); }
|
|
66
|
+
.alert-danger { border: 1px solid var(--danger-line); background: var(--danger-bg); border-radius: var(--r-control); padding: 10px 12px; margin-bottom: 12px; }
|
|
67
|
+
.alert-danger strong { color: var(--danger); }
|
|
68
|
+
.diff-table td.before { color: var(--danger); } .diff-table td.after { color: var(--success); }
|
|
69
|
+
.row-added td { background: var(--success-bg); } .row-dropped td { background: var(--danger-bg); } .row-altered td { background: var(--active-bg); }
|
|
70
|
+
.chip-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
71
|
+
.chip { font-family: var(--mono); font-size: 12px; padding: 1px 8px; border-radius: var(--r-control); border: 1px solid var(--chip-border); background: var(--chip); }
|
|
72
|
+
|
|
73
|
+
.sql-wrap { position: relative; padding: 0; overflow: hidden; }
|
|
74
|
+
.sql-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--border); }
|
|
75
|
+
pre.sql { margin: 0; padding: 16px; overflow: auto; max-height: 70vh; background: var(--bg); font-family: var(--mono); font-size: 12.5px; line-height: 1.6; white-space: pre; }
|
|
76
|
+
.sql .k { color: var(--primary); font-weight: 600; } .sql .s { color: var(--success); } .sql .c { color: var(--muted); font-style: italic; } .sql .d { color: var(--danger); font-weight: 700; }
|
|
77
|
+
.sql .p { color: var(--active); } .sql .n { color: var(--active); font-weight: 600; } .sql .o { color: var(--primary); }
|
|
78
|
+
.lang { font-family: var(--mono); font-size: 11px; padding: 0 7px; border-radius: var(--r-control); border: 1px solid var(--primary-line); background: var(--primary-tint); color: var(--primary); margin-right: 6px; }
|
|
79
|
+
.copy-wrap { position: relative; display: inline-flex; }
|
|
80
|
+
.tooltip { position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%); background: var(--success); color: #fff; font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: var(--r-control); pointer-events: none; opacity: 0; transition: opacity .15s; white-space: nowrap; }
|
|
81
|
+
.tooltip.show { opacity: 1; }
|
|
82
|
+
|
|
83
|
+
.empty { text-align: center; padding: 48px 24px; }
|
|
84
|
+
.empty .empty-icon { color: var(--primary); margin: 0 auto 12px; }
|
|
85
|
+
.empty h3 { margin: 0 0 6px; font-size: 16px; }
|
|
86
|
+
.empty p { color: var(--muted); margin: 0 0 16px; font-size: 15px; line-height: 1.55; }
|
|
87
|
+
|
|
88
|
+
.env-toggle-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin: 14px 0 6px; }
|
|
89
|
+
.env-segmented { display: inline-flex; gap: 2px; padding: 3px; background: var(--chip); border: 1px solid var(--border); border-radius: var(--r-control); }
|
|
90
|
+
.env-seg-btn { border: 1px solid transparent; background: transparent; padding: 4px 14px; border-radius: 5px; cursor: pointer; color: var(--muted); font-size: 13px; font-weight: 600; transition: all .15s ease; }
|
|
91
|
+
.env-seg-btn:hover { color: var(--text); }
|
|
92
|
+
.env-seg-btn[aria-pressed="true"] { background: var(--surface); color: var(--primary); border-color: var(--border); box-shadow: var(--shadow); }
|
|
93
|
+
.env-seg-btn[data-env-mode="prod"][aria-pressed="true"] { color: var(--danger); background: var(--danger-bg); border-color: var(--danger-line); }
|
|
94
|
+
.prod-banner { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-radius: var(--r-control); border: 1px solid var(--danger-line); background: var(--danger-bg); font-size: 13px; margin-bottom: 14px; }
|
|
95
|
+
.prod-banner b { color: var(--danger); font-weight: 600; }
|
|
96
|
+
|
|
97
|
+
.tabs { display: flex; gap: 4px; margin: 16px 0; border-bottom: 1px solid var(--border); overflow-x: auto; overflow-y: hidden; }
|
|
98
|
+
.subtab { background: none; border: none; padding: 10px 12px; cursor: pointer; color: var(--muted); border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap; display: inline-flex; align-items: center; gap: 8px; font-size: 13.5px; font-weight: 500; }
|
|
99
|
+
.subtab:hover { color: var(--text); }
|
|
100
|
+
.subtab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--primary); }
|
|
101
|
+
|
|
102
|
+
.data-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
|
|
103
|
+
.data-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; flex: 1; min-width: 0; }
|
|
104
|
+
.data-grid-wrap { overflow-x: auto; max-width: 100%; border: 1px solid var(--border); border-radius: var(--r-card); background: var(--surface); box-shadow: var(--shadow); margin-bottom: 14px; min-height: 240px; }
|
|
105
|
+
table.data-table { width: 100%; border-collapse: collapse; font-size: 13px; text-align: left; }
|
|
106
|
+
table.data-table th { background: var(--bg); color: var(--muted); font-weight: 600; font-size: 12.5px; padding: 10px 12px; border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 2; white-space: nowrap; user-select: none; }
|
|
107
|
+
table.data-table th.sortable { cursor: pointer; }
|
|
108
|
+
table.data-table th.sortable:hover { color: var(--text); background: var(--chip); }
|
|
109
|
+
table.data-table td { padding: 8px 12px; border-bottom: 1px solid var(--border); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
|
|
110
|
+
table.data-table tr:hover td { background: var(--bg); }
|
|
111
|
+
.th-sort-icon { font-size: 10px; margin-left: 4px; color: var(--primary); }
|
|
112
|
+
.td-actions { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
|
|
113
|
+
.pagination-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 4px; font-size: 13px; color: var(--muted); }
|
|
114
|
+
.pagination-ctrls { display: flex; align-items: center; gap: 8px; }
|
|
115
|
+
.cell-preview-btn { padding: 1px 6px; font-size: 11px; border-radius: 4px; border: 1px solid var(--primary-line); color: var(--primary); background: var(--primary-tint); cursor: pointer; }
|
|
116
|
+
.cell-preview-btn:hover { background: var(--surface); }
|
|
117
|
+
|
|
118
|
+
`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ICON_PATHS } from '../../shell/icons.js';
|
|
2
|
+
import { flowScript } from './script.js';
|
|
3
|
+
import { flowCss } from './styles.js';
|
|
4
|
+
export const flowPage = {
|
|
5
|
+
id: 'flow',
|
|
6
|
+
group: 'now',
|
|
7
|
+
label: 'Flow',
|
|
8
|
+
icon: ICON_PATHS.flow,
|
|
9
|
+
css: flowCss,
|
|
10
|
+
script: flowScript,
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const flowLayoutScript: string;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Plan layout: rounds, lanes, links and the highlight set. Pure, no DOM.
|
|
2
|
+
export const flowLayoutScript = String.raw ` var FLOW = { W: 206, H: 58, GX: 52, GY: 10, PAD: 14, LX: 128, TOP: 30 };
|
|
3
|
+
|
|
4
|
+
function flowAgentKey(t) { return t.agent || 'unassigned'; }
|
|
5
|
+
|
|
6
|
+
function flowGraph(m) {
|
|
7
|
+
var ids = {};
|
|
8
|
+
m.tasks.forEach(function (t) { ids[t.id] = t; });
|
|
9
|
+
var depsIn = function (id) {
|
|
10
|
+
return ids[id].deps.filter(function (d) { return ids[d] && d !== id; });
|
|
11
|
+
};
|
|
12
|
+
var depth = {}, anc = {};
|
|
13
|
+
function getD(id, seen) {
|
|
14
|
+
if (depth[id] != null) return depth[id];
|
|
15
|
+
if (seen[id]) return 0;
|
|
16
|
+
seen[id] = true;
|
|
17
|
+
var d = 0;
|
|
18
|
+
depsIn(id).forEach(function (p) { d = Math.max(d, 1 + getD(p, seen)); });
|
|
19
|
+
delete seen[id];
|
|
20
|
+
depth[id] = d;
|
|
21
|
+
return d;
|
|
22
|
+
}
|
|
23
|
+
function getA(id, seen) {
|
|
24
|
+
if (anc[id]) return anc[id];
|
|
25
|
+
if (seen[id]) return {};
|
|
26
|
+
seen[id] = true;
|
|
27
|
+
var out = {};
|
|
28
|
+
depsIn(id).forEach(function (p) {
|
|
29
|
+
out[p] = true;
|
|
30
|
+
var up = getA(p, seen);
|
|
31
|
+
Object.keys(up).forEach(function (k) { out[k] = true; });
|
|
32
|
+
});
|
|
33
|
+
delete seen[id];
|
|
34
|
+
anc[id] = out;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
m.tasks.forEach(function (t) { getD(t.id, {}); getA(t.id, {}); });
|
|
38
|
+
var edges = [], kids = {};
|
|
39
|
+
m.tasks.forEach(function (t) {
|
|
40
|
+
depsIn(t.id).forEach(function (d) {
|
|
41
|
+
var implied = depsIn(t.id).some(function (e) { return e !== d && getA(e, {})[d]; });
|
|
42
|
+
edges.push({ from: d, to: t.id, implied: implied });
|
|
43
|
+
(kids[d] = kids[d] || []).push(t.id);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
return { depth: depth, anc: function (id) { return getA(id, {}); }, edges: edges, kids: kids };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function flowPlace(m, g) {
|
|
50
|
+
var lanes = [], cells = {};
|
|
51
|
+
m.tasks.forEach(function (t) {
|
|
52
|
+
var a = flowAgentKey(t);
|
|
53
|
+
if (lanes.indexOf(a) === -1) lanes.push(a);
|
|
54
|
+
var k = a + '|' + g.depth[t.id];
|
|
55
|
+
(cells[k] = cells[k] || []).push(t);
|
|
56
|
+
});
|
|
57
|
+
var rounds = Math.max.apply(null, m.tasks.map(function (t) { return g.depth[t.id]; })) + 1;
|
|
58
|
+
var laneY = {}, laneH = {}, y = FLOW.TOP;
|
|
59
|
+
lanes.forEach(function (l) {
|
|
60
|
+
var stack = 1;
|
|
61
|
+
for (var r = 0; r < rounds; r++) stack = Math.max(stack, (cells[l + '|' + r] || []).length);
|
|
62
|
+
laneY[l] = y;
|
|
63
|
+
laneH[l] = FLOW.PAD * 2 + stack * FLOW.H + (stack - 1) * FLOW.GY;
|
|
64
|
+
y += laneH[l];
|
|
65
|
+
});
|
|
66
|
+
var pos = {};
|
|
67
|
+
Object.keys(cells).forEach(function (k) {
|
|
68
|
+
var bits = k.split('|'), r = Number(bits[1]);
|
|
69
|
+
cells[k].forEach(function (t, i) {
|
|
70
|
+
pos[t.id] = { x: FLOW.LX + 20 + r * (FLOW.W + FLOW.GX), y: laneY[bits[0]] + FLOW.PAD + i * (FLOW.H + FLOW.GY), round: r + 1 };
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return { lanes: lanes, laneY: laneY, laneH: laneH, pos: pos, rounds: rounds,
|
|
74
|
+
width: FLOW.LX + 20 + rounds * (FLOW.W + FLOW.GX), height: y };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Everything the Plan view draws for one milestone. */
|
|
78
|
+
function flowLayout(m) {
|
|
79
|
+
var g = flowGraph(m);
|
|
80
|
+
var L = flowPlace(m, g);
|
|
81
|
+
L.edges = g.edges;
|
|
82
|
+
L.kids = g.kids;
|
|
83
|
+
L.anc = g.anc;
|
|
84
|
+
L.hidden = g.edges.filter(function (e) { return e.implied; }).length;
|
|
85
|
+
return L;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The task, everything it needs and everything it unblocks, or null when it is not drawn. */
|
|
89
|
+
function flowFocusSet(L, id) {
|
|
90
|
+
if (!id || !L.pos[id]) return null;
|
|
91
|
+
var hot = {}, down = {}, stack = [id];
|
|
92
|
+
hot[id] = true;
|
|
93
|
+
Object.keys(L.anc(id)).forEach(function (k) { hot[k] = true; });
|
|
94
|
+
while (stack.length) {
|
|
95
|
+
(L.kids[stack.pop()] || []).forEach(function (c) {
|
|
96
|
+
if (down[c]) return;
|
|
97
|
+
down[c] = true;
|
|
98
|
+
hot[c] = true;
|
|
99
|
+
stack.push(c);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return hot;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const flowPlanScript: string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Plan view: swimlanes per agent, one column per round, bezier links inside the milestone.
|
|
2
|
+
export const flowPlanScript = String.raw ` function flowWires(L, hot, showAll) {
|
|
3
|
+
return L.edges.filter(function (e) { return showAll || !e.implied; }).map(function (e) {
|
|
4
|
+
var a = L.pos[e.from], b = L.pos[e.to];
|
|
5
|
+
var x1 = a.x + FLOW.W, y1 = a.y + FLOW.H / 2, x2 = b.x, y2 = b.y + FLOW.H / 2;
|
|
6
|
+
var dx = Math.max(28, (x2 - x1) / 2);
|
|
7
|
+
var on = hot && hot[e.from] && hot[e.to];
|
|
8
|
+
return '<path class="wire' + (on ? ' hot' : '') + (e.implied ? ' implied' : '') + '" data-from="' + ui.esc(e.from) +
|
|
9
|
+
'" data-to="' + ui.esc(e.to) + '" d="M' + x1 + ' ' + y1 + ' C' + (x1 + dx) + ' ' + y1 + ', ' + (x2 - dx) + ' ' + y2 + ', ' + x2 + ' ' + y2 + '"/>';
|
|
10
|
+
}).join('');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function flowBands(m, L) {
|
|
14
|
+
return L.lanes.map(function (l) {
|
|
15
|
+
var count = m.tasks.filter(function (t) { return flowAgentKey(t) === l; }).length;
|
|
16
|
+
return '<div class="lane-band" data-lane="' + ui.esc(l) + '" style="top:' + L.laneY[l] + 'px;height:' + L.laneH[l] + 'px">' +
|
|
17
|
+
'<div class="lane-label">' + ui.agentChip(l === 'unassigned' ? null : l) + '<span class="faint">' + count + (count === 1 ? ' task' : ' tasks') + '</span></div></div>';
|
|
18
|
+
}).join('');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function flowRoundHeads(L) {
|
|
22
|
+
var out = '';
|
|
23
|
+
for (var r = 0; r < L.rounds; r++) {
|
|
24
|
+
out += '<div class="round-head" style="left:' + (FLOW.LX + 20 + r * (FLOW.W + FLOW.GX)) + 'px">Round ' + (r + 1) + '</div>';
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function flowNodeTime(t) {
|
|
30
|
+
if (t.status === 'in_progress' && t.startT) return '<span data-since="' + t.startT + '">' + ui.esc(fmt.dur(Studio.now() - t.startT)) + '</span>';
|
|
31
|
+
return t.durationMs ? ui.esc(fmt.dur(t.durationMs)) : '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function flowNodes(m, L, hot, focusId) {
|
|
35
|
+
return m.tasks.map(function (t) {
|
|
36
|
+
var p = L.pos[t.id];
|
|
37
|
+
var cls = 'node ' + t.status + (hot && hot[t.id] ? ' hot' : '') + (focusId === t.id ? ' sel' : '');
|
|
38
|
+
return '<button type="button" class="' + ui.esc(cls) + '" data-act="flow-focus" data-key="' + ui.esc(t.id) + '" data-id="' + ui.esc(t.id) +
|
|
39
|
+
'" data-round="' + p.round + '" aria-pressed="' + (focusId === t.id) + '" title="' + ui.esc(t.title) + '" style="left:' + p.x + 'px;top:' + p.y +
|
|
40
|
+
'px;width:' + FLOW.W + 'px;height:' + FLOW.H + 'px"><div class="l1">' + ui.statusIcon(t.status) + '<span class="mono faint">' + ui.esc(t.id) +
|
|
41
|
+
'</span><span class="d">' + flowNodeTime(t) + '</span></div><div class="title">' + ui.esc(t.title) + '</div></button>';
|
|
42
|
+
}).join('');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function flowLegend() {
|
|
46
|
+
var one = function (s) { return '<span>' + ui.statusIcon(s) + Studio.statusLabel(s) + '</span>'; };
|
|
47
|
+
return '<div class="legend">' + ['completed', 'in_progress', 'pending', 'blocked'].map(one).join('') + '</div>';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function flowPlanTools(L, hot, view) {
|
|
51
|
+
var hint = hot ? '<button type="button" class="btn ghost" data-act="flow-clear">Clear highlight</button>'
|
|
52
|
+
: '<span>Click a task to light up what it needs and what it unblocks</span>';
|
|
53
|
+
var implied = L.hidden ? '<label class="check"><input type="checkbox" data-act="flow-implied"' + (view.implied ? ' checked' : '') +
|
|
54
|
+
'> Show ' + L.hidden + ' implied link' + (L.hidden > 1 ? 's' : '') + '</label>' : '';
|
|
55
|
+
return '<div class="fl-bar">' + flowLegend() + '<span class="right">' + hint + implied + '</span></div>';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function flowPlan(m, focusId, view) {
|
|
59
|
+
var L = flowLayout(m);
|
|
60
|
+
var hot = flowFocusSet(L, focusId);
|
|
61
|
+
return '<section class="fl-panel" data-mode="plan">' + flowPlanTools(L, hot, view) +
|
|
62
|
+
'<div class="flow-wrap"><div class="canvas' + (hot ? ' focus' : '') + '" style="width:' + L.width + 'px;height:' + (L.height + 10) + 'px">' +
|
|
63
|
+
flowBands(m, L) + flowRoundHeads(L) + '<svg class="wires" width="' + L.width + '" height="' + L.height + '">' +
|
|
64
|
+
flowWires(L, hot, view.implied) + '</svg>' + flowNodes(m, L, hot, hot ? focusId : null) + '</div></div></section>';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const flowScript: string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { flowLayoutScript } from './layout.js';
|
|
2
|
+
import { flowPlanScript } from './plan-view.js';
|
|
3
|
+
import { flowTimeModelScript } from './timeline-model.js';
|
|
4
|
+
import { flowTimelineScript } from './timeline-view.js';
|
|
5
|
+
// Flow page: registration, header and click handling. Views live in the sibling files.
|
|
6
|
+
const flowMainScript = String.raw ` var ui = Studio.ui, fmt = Studio.fmt;
|
|
7
|
+
var view = { mode: 'plan', implied: false };
|
|
8
|
+
|
|
9
|
+
function flowHead(m) {
|
|
10
|
+
var seg = function (mode, label) {
|
|
11
|
+
return '<button type="button" data-act="flow-mode" data-mode="' + mode + '" aria-pressed="' + (view.mode === mode) + '">' + label + '</button>';
|
|
12
|
+
};
|
|
13
|
+
return '<div class="panel-head"><div class="panel-title"><h1>Flow</h1><p class="lead">' +
|
|
14
|
+
(m ? ui.esc(m.name) : 'How the work in one milestone connects and how it actually ran.') + '</p></div>' +
|
|
15
|
+
'<div class="panel-actions">' + ui.milestoneSelect() + '<div class="seg" role="group" aria-label="Flow view">' +
|
|
16
|
+
seg('plan', 'Plan') + seg('timeline', 'What happened') + '</div></div></div>';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function flowShowFocus() {
|
|
20
|
+
var id = Studio.state.focusTaskId;
|
|
21
|
+
if (!id || typeof document === 'undefined') return;
|
|
22
|
+
setTimeout(function () {
|
|
23
|
+
var el = document.querySelector('#panel-flow [data-id="' + String(id).replace(/["\\]/g, '') + '"]');
|
|
24
|
+
if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest', inline: 'center' });
|
|
25
|
+
}, 0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Studio.registerPage('flow', {
|
|
29
|
+
deps: ['tasks', 'telemetry', 'runs'],
|
|
30
|
+
onShow: flowShowFocus,
|
|
31
|
+
render: function (ctx) {
|
|
32
|
+
var wanted = ctx.state.milestoneId || ctx.model.activeMilestoneId;
|
|
33
|
+
var m = ctx.model.milestones.filter(function (x) { return x.id === wanted; })[0];
|
|
34
|
+
if (!m) return flowHead(null) + ui.empty('No milestones yet', 'Add tasks to the plan to see them here.');
|
|
35
|
+
if (ctx.state.focusTaskId) view.mode = 'plan';
|
|
36
|
+
var body = !m.tasks.length ? ui.empty('This milestone has no tasks yet.', '')
|
|
37
|
+
: view.mode === 'plan' ? flowPlan(m, ctx.state.focusTaskId, view) : flowTimeline(m, ctx);
|
|
38
|
+
return flowHead(m) + body;
|
|
39
|
+
},
|
|
40
|
+
actions: {
|
|
41
|
+
'flow-mode': function (el) {
|
|
42
|
+
view.mode = el.getAttribute('data-mode') === 'timeline' ? 'timeline' : 'plan';
|
|
43
|
+
Studio.state.focusTaskId = null;
|
|
44
|
+
Studio.repaint('flow');
|
|
45
|
+
},
|
|
46
|
+
'flow-focus': function (el) {
|
|
47
|
+
var id = el.getAttribute('data-id');
|
|
48
|
+
if (Studio.state.focusTaskId === id) { Studio.closeDetail(); return; }
|
|
49
|
+
Studio.state.focusTaskId = id;
|
|
50
|
+
Studio.openTask(id);
|
|
51
|
+
Studio.repaint('flow');
|
|
52
|
+
},
|
|
53
|
+
'flow-clear': function () { Studio.closeDetail(); },
|
|
54
|
+
'flow-implied': function (el) {
|
|
55
|
+
view.implied = !!el.checked;
|
|
56
|
+
Studio.repaint('flow');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
`;
|
|
61
|
+
export const flowScript = [flowLayoutScript, flowPlanScript, flowTimeModelScript, flowTimelineScript, flowMainScript].join('');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const flowCss: string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Flow page styles, all scoped under #panel-flow.
|
|
2
|
+
export const flowCss = String.raw `#panel-flow .seg { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--surface); }
|
|
3
|
+
#panel-flow .seg button { border: 0; background: none; padding: 0 12px; height: 30px; font-size: 13px; color: var(--fg-2); cursor: pointer; }
|
|
4
|
+
#panel-flow .seg button + button { border-left: 1px solid var(--line); }
|
|
5
|
+
#panel-flow .seg button[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); font-weight: 500; }
|
|
6
|
+
#panel-flow .fl-panel { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; min-width: 0; overflow: hidden; }
|
|
7
|
+
#panel-flow .fl-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; padding: 12px 16px; }
|
|
8
|
+
#panel-flow .fl-bar .right { margin-left: auto; display: flex; gap: 14px; align-items: center; flex-wrap: wrap; font-size: 12.5px; color: var(--fg-3); }
|
|
9
|
+
#panel-flow .flow-wrap { overflow: auto; max-height: 72vh; position: relative; border-top: 1px solid var(--line); }
|
|
10
|
+
#panel-flow .canvas { position: relative; }
|
|
11
|
+
#panel-flow .canvas svg.wires { position: absolute; inset: 0; pointer-events: none; overflow: visible; }
|
|
12
|
+
#panel-flow .wire { fill: none; stroke: var(--line-2); stroke-width: 1.5; transition: opacity .15s, stroke .15s; }
|
|
13
|
+
#panel-flow .wire.implied { stroke-dasharray: 4 4; }
|
|
14
|
+
#panel-flow .wire.hot { stroke: var(--accent); stroke-width: 2; }
|
|
15
|
+
#panel-flow .canvas.focus .wire:not(.hot) { opacity: .15; }
|
|
16
|
+
#panel-flow .lane-band { position: absolute; left: 0; right: 0; border-top: 1px solid var(--line); }
|
|
17
|
+
#panel-flow .lane-band:nth-child(even) { background: color-mix(in srgb, var(--surface-2) 45%, transparent); }
|
|
18
|
+
#panel-flow .lane-label { position: sticky; left: 0; width: 128px; padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; background: var(--surface); border-right: 1px solid var(--line); height: 100%; z-index: 2; }
|
|
19
|
+
#panel-flow .lane-label .faint { font-size: 11.5px; }
|
|
20
|
+
#panel-flow .round-head { position: absolute; top: 0; height: 30px; display: flex; align-items: center; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-3); }
|
|
21
|
+
#panel-flow .node { position: absolute; display: flex; flex-direction: column; justify-content: center; gap: 3px; padding: 7px 10px; background: var(--surface); border: 1px solid var(--line); border-radius: 9px; text-align: left; cursor: pointer; transition: opacity .15s, border-color .15s, box-shadow .15s; z-index: 1; }
|
|
22
|
+
#panel-flow .node:hover { border-color: var(--line-2); box-shadow: var(--shadow); }
|
|
23
|
+
#panel-flow .node.in_progress { border-color: color-mix(in srgb, var(--run) 50%, var(--line)); background: color-mix(in srgb, var(--run-soft) 55%, var(--surface)); }
|
|
24
|
+
#panel-flow .node.blocked { border-color: color-mix(in srgb, var(--bad) 50%, var(--line)); background: color-mix(in srgb, var(--bad-soft) 55%, var(--surface)); }
|
|
25
|
+
#panel-flow .node .l1 { display: flex; align-items: center; gap: 6px; }
|
|
26
|
+
#panel-flow .node .l1 .d { margin-left: auto; font: 11px var(--mono); color: var(--fg-3); }
|
|
27
|
+
#panel-flow .node .l1 .mono { font-size: 11px; }
|
|
28
|
+
#panel-flow .node .title { font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
29
|
+
#panel-flow .canvas.focus .node:not(.hot) { opacity: .3; }
|
|
30
|
+
#panel-flow .node.sel { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
31
|
+
#panel-flow .legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--fg-2); align-items: center; }
|
|
32
|
+
#panel-flow .legend span { display: inline-flex; align-items: center; gap: 6px; }
|
|
33
|
+
#panel-flow .check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--fg-2); }
|
|
34
|
+
#panel-flow .stats { display: flex; flex-wrap: wrap; }
|
|
35
|
+
#panel-flow .stats > div { padding: 12px 16px; display: flex; flex-direction: column; gap: 1px; min-width: 140px; }
|
|
36
|
+
#panel-flow .stats > div + div { border-left: 1px solid var(--line); }
|
|
37
|
+
#panel-flow .stats .k { font-size: 12px; color: var(--fg-3); }
|
|
38
|
+
#panel-flow .stats .v { font-size: 17px; font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
39
|
+
#panel-flow .stats .s { font-size: 12px; color: var(--fg-2); }
|
|
40
|
+
#panel-flow svg.tl { display: block; }
|
|
41
|
+
#panel-flow svg.tl text { font-family: var(--font); font-size: 11px; fill: var(--fg-3); }
|
|
42
|
+
#panel-flow svg.tl .lane-t { fill: var(--fg); font-weight: 500; font-size: 12px; }
|
|
43
|
+
#panel-flow svg.tl .grid { stroke: var(--line); stroke-width: 1; }
|
|
44
|
+
#panel-flow svg.tl .band { fill: var(--surface-2); opacity: .5; }
|
|
45
|
+
#panel-flow svg.tl .gap { fill: var(--surface-2); }
|
|
46
|
+
#panel-flow svg.tl .gap-line { stroke: var(--line-2); stroke-dasharray: 3 3; }
|
|
47
|
+
#panel-flow svg.tl .bar-w { fill: color-mix(in srgb, var(--ok) 30%, var(--surface)); stroke: var(--ok); stroke-width: 1; cursor: pointer; }
|
|
48
|
+
#panel-flow svg.tl .bar-c { fill: var(--ok); pointer-events: none; }
|
|
49
|
+
#panel-flow svg.tl .bar-w.in_progress { fill: url(#fl-stripes); stroke: var(--run); }
|
|
50
|
+
#panel-flow svg.tl .bar-w.blocked { fill: color-mix(in srgb, var(--bad) 22%, var(--surface)); stroke: var(--bad); }
|
|
51
|
+
#panel-flow svg.tl .bar-w:hover { stroke-width: 2; }
|
|
52
|
+
#panel-flow svg.tl .bar-l { fill: var(--fg); font-size: 11px; pointer-events: none; font-family: var(--mono); }
|
|
53
|
+
#panel-flow svg.tl .now-line { stroke: var(--run); stroke-width: 1.5; }
|
|
54
|
+
#panel-flow svg.tl .stripe-a { fill: var(--run-soft); }
|
|
55
|
+
#panel-flow svg.tl .stripe-b { fill: color-mix(in srgb, var(--run) 30%, var(--surface)); }
|
|
56
|
+
#panel-flow .fl-untimed { padding: 12px 16px; border-top: 1px solid var(--line); font-size: 12.5px; color: var(--fg-3); }
|
|
57
|
+
#panel-flow .fl-untimed button { border: 0; background: none; padding: 0; color: var(--fg-2); cursor: pointer; }
|
|
58
|
+
#panel-flow .fl-untimed button:hover { text-decoration: underline; }
|
|
59
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const flowTimeModelScript: string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Timeline numbers: bar times, squeezed idle gaps, agent rows and summary figures. Pure.
|
|
2
|
+
export const flowTimeModelScript = String.raw ` var TL = { GAP: 15 * 60000, MIN_BAR: 15000, LW: 130, GW: 40, ROWH: 30, TOPY: 30 };
|
|
3
|
+
|
|
4
|
+
function flowEnd(t, now) {
|
|
5
|
+
if (t.status === 'in_progress') return Math.max(now, t.startT + 1000);
|
|
6
|
+
if (t.endT && t.endT > t.startT) return t.endT;
|
|
7
|
+
return t.startT + (t.durationMs || TL.MIN_BAR);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Merges sorted [start, end] pairs; touching within slack counts as one stretch. */
|
|
11
|
+
function flowMerge(iv, slack) {
|
|
12
|
+
var out = [];
|
|
13
|
+
iv.forEach(function (p) {
|
|
14
|
+
var last = out[out.length - 1];
|
|
15
|
+
if (last && p[0] - last[1] <= slack) last[1] = Math.max(last[1], p[1]);
|
|
16
|
+
else out.push([p[0], p[1]]);
|
|
17
|
+
});
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function flowSummary(timed, now) {
|
|
22
|
+
var iv = timed.map(function (t) { return [t.startT, flowEnd(t, now)]; }).sort(function (a, b) { return a[0] - b[0]; });
|
|
23
|
+
var busy = flowMerge(iv, 0);
|
|
24
|
+
var busyMs = busy.reduce(function (s, p) { return s + p[1] - p[0]; }, 0);
|
|
25
|
+
var last = Math.max.apply(null, iv.map(function (p) { return p[1]; }));
|
|
26
|
+
var span = last - iv[0][0];
|
|
27
|
+
var longest = 0, at = null;
|
|
28
|
+
for (var i = 1; i < busy.length; i++) {
|
|
29
|
+
var g = busy[i][0] - busy[i - 1][1];
|
|
30
|
+
if (g > longest) { longest = g; at = busy[i - 1][1]; }
|
|
31
|
+
}
|
|
32
|
+
var peak = 0;
|
|
33
|
+
iv.forEach(function (p) {
|
|
34
|
+
var c = iv.filter(function (o) { return o[0] <= p[0] && o[1] > p[0]; }).length;
|
|
35
|
+
peak = Math.max(peak, c);
|
|
36
|
+
});
|
|
37
|
+
return { iv: iv, start: iv[0][0], span: span, busyMs: busyMs, busyPct: Math.round(busyMs / (span || 1) * 100),
|
|
38
|
+
peak: peak, longest: longest, longestAt: at };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Time segments left to right: real stretches plus fixed-width bands for idle gaps over 15 minutes. */
|
|
42
|
+
function flowSegments(iv, width) {
|
|
43
|
+
var merged = flowMerge(iv, TL.GAP);
|
|
44
|
+
var plotW = width - TL.LW - 24 - (merged.length - 1) * TL.GW;
|
|
45
|
+
var active = merged.reduce(function (s, p) { return s + p[1] - p[0]; }, 0) || 1;
|
|
46
|
+
var k = Math.max(plotW, 120) / active, segs = [], x = TL.LW;
|
|
47
|
+
merged.forEach(function (p, i) {
|
|
48
|
+
if (i) {
|
|
49
|
+
segs.push({ gap: true, t0: merged[i - 1][1], t1: p[0], x0: x, x1: x + TL.GW });
|
|
50
|
+
x += TL.GW;
|
|
51
|
+
}
|
|
52
|
+
segs.push({ t0: p[0], t1: p[1], x0: x, x1: x + (p[1] - p[0]) * k });
|
|
53
|
+
x += (p[1] - p[0]) * k;
|
|
54
|
+
});
|
|
55
|
+
var X = function (t) {
|
|
56
|
+
var s = segs.filter(function (q) { return t >= q.t0 && t <= q.t1; })[0] || (t < segs[0].t0 ? segs[0] : segs[segs.length - 1]);
|
|
57
|
+
var scale = s.gap ? TL.GW / (s.t1 - s.t0) : k;
|
|
58
|
+
return s.x0 + Math.max(0, Math.min(t, s.t1) - s.t0) * scale;
|
|
59
|
+
};
|
|
60
|
+
return { segs: segs, k: k, X: X, right: x };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Overlapping tasks of one agent go on extra rows. */
|
|
64
|
+
function flowRows(list, now) {
|
|
65
|
+
var rows = [], place = {};
|
|
66
|
+
list.slice().sort(function (a, b) { return a.startT - b.startT; }).forEach(function (t) {
|
|
67
|
+
var r = 0;
|
|
68
|
+
while (r < rows.length && rows[r] > t.startT) r++;
|
|
69
|
+
rows[r] = flowEnd(t, now);
|
|
70
|
+
place[t.id] = r;
|
|
71
|
+
});
|
|
72
|
+
return { count: Math.max(1, rows.length), place: place };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function flowTimeModel(m, now, outer) {
|
|
76
|
+
var timed = m.tasks.filter(function (t) { return t.startT; });
|
|
77
|
+
if (!timed.length) return null;
|
|
78
|
+
var sum = flowSummary(timed, now);
|
|
79
|
+
var width = Math.max(760, (outer || 1000) - 2);
|
|
80
|
+
var sg = flowSegments(sum.iv, width);
|
|
81
|
+
var lanes = [], y = TL.TOPY;
|
|
82
|
+
m.tasks.forEach(function (t) {
|
|
83
|
+
var a = flowAgentKey(t);
|
|
84
|
+
if (lanes.some(function (l) { return l.key === a; })) return;
|
|
85
|
+
var list = timed.filter(function (o) { return flowAgentKey(o) === a; });
|
|
86
|
+
if (!list.length) return;
|
|
87
|
+
var rows = flowRows(list, now);
|
|
88
|
+
lanes.push({ key: a, y: y, h: rows.count * TL.ROWH + 12, place: rows.place, tasks: list });
|
|
89
|
+
y += rows.count * TL.ROWH + 12;
|
|
90
|
+
});
|
|
91
|
+
return { timed: timed, untimed: m.tasks.filter(function (t) { return !t.startT; }), sum: sum, sg: sg,
|
|
92
|
+
lanes: lanes, width: width, height: y + 8, now: now };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const flowTimelineScript: string;
|