@worca/ui 0.23.0 → 0.24.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/app/protocol.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Protocol definitions for worca-ui WebSocket communication.
3
3
  */
4
4
 
5
- /** @typedef {'subscribe-run'|'unsubscribe-run'|'subscribe-log'|'unsubscribe-log'|'list-runs'|'get-agent-prompt'|'get-preferences'|'set-preferences'|'stop-run'|'resume-run'|'list-beads-issues'|'start-beads-issue'|'list-beads-counts'|'list-beads-refs'|'list-beads-unlinked'|'run-snapshot'|'run-update'|'runs-list'|'log-line'|'log-bulk'|'preferences'|'run-started'|'run-stopped'|'stage-restarted'|'beads-update'|'fleet-update'|'hello'|'hello-ack'} MessageType */
5
+ /** @typedef {'subscribe-run'|'unsubscribe-run'|'subscribe-log'|'unsubscribe-log'|'list-runs'|'get-agent-prompt'|'get-preferences'|'set-preferences'|'stop-run'|'resume-run'|'list-beads-issues'|'start-beads-issue'|'list-beads-counts'|'list-beads-refs'|'list-beads-unlinked'|'run-snapshot'|'run-update'|'runs-list'|'log-line'|'log-bulk'|'preferences'|'run-started'|'run-stopped'|'stage-restarted'|'beads-update'|'fleet-update'|'workspace-update'|'workspace-tier-update'|'guide-conflict'|'hello'|'hello-ack'} MessageType */
6
6
 
7
7
  /** @type {MessageType[]} */
8
8
  export const MESSAGE_TYPES = [
@@ -41,6 +41,9 @@ export const MESSAGE_TYPES = [
41
41
  'stage-restarted',
42
42
  'beads-update',
43
43
  'fleet-update',
44
+ 'workspace-update',
45
+ 'workspace-tier-update',
46
+ 'guide-conflict',
44
47
  'webhook-inbox-event',
45
48
  'webhook-control-changed',
46
49
  'webhook-inbox-cleared',
package/app/styles.css CHANGED
@@ -535,14 +535,13 @@ h1, h2, h3, h4, h5, h6 {
535
535
  gap: 6px;
536
536
  }
537
537
 
538
- .run-branch {
539
- display: flex;
540
- align-items: center;
541
- gap: 6px;
542
- font-size: 13px;
543
- }
544
-
545
- .run-template {
538
+ /* Project / Branch / Pipeline-Template / Worktree rows inside
539
+ `.run-info-section` all share the same label-value typography so the
540
+ block reads as a uniform stack. Mirror .run-group below it. */
541
+ .run-project,
542
+ .run-branch,
543
+ .run-template,
544
+ .run-worktree {
546
545
  display: flex;
547
546
  align-items: center;
548
547
  gap: 6px;
@@ -1162,6 +1161,22 @@ sl-input [slot="prefix"] {
1162
1161
  color: var(--status-failed);
1163
1162
  }
1164
1163
 
1164
+ .status-planning {
1165
+ color: var(--status-running);
1166
+ }
1167
+
1168
+ .status-integration-testing {
1169
+ color: var(--status-running);
1170
+ }
1171
+
1172
+ .status-integration-failed {
1173
+ color: var(--status-failed);
1174
+ }
1175
+
1176
+ .status-blocked {
1177
+ color: var(--status-paused);
1178
+ }
1179
+
1165
1180
  /* --- 18b. Control Buttons --- */
1166
1181
  .action-btn--amber {
1167
1182
  border-color: var(--status-paused);
@@ -1339,6 +1354,10 @@ sl-input [slot="prefix"] {
1339
1354
  .run-card.status-halted { border-left: 3px solid var(--status-paused); }
1340
1355
  .run-card.status-setup-failed { border-left: 3px solid var(--status-failed); }
1341
1356
  .run-card.status-unrecoverable { border-left: 3px solid var(--status-failed); }
1357
+ .run-card.status-planning { border-left: 3px solid var(--status-running); }
1358
+ .run-card.status-integration-testing { border-left: 3px solid var(--status-running); }
1359
+ .run-card.status-integration-failed { border-left: 3px solid var(--status-failed); }
1360
+ .run-card.status-blocked { border-left: 3px solid var(--status-paused); }
1342
1361
 
1343
1362
  /* ── Fleet card (shared component for Dashboard + Fleet list) ───────────
1344
1363
  The fleet card reuses the `.run-card` base class and its shared
@@ -4226,6 +4245,8 @@ sl-details.learnings-panel::part(content) {
4226
4245
  .filter-chip-paused.active { background: var(--status-paused); border-color: var(--status-paused); }
4227
4246
  .filter-chip-error.active { background: var(--status-failed); border-color: var(--status-failed); }
4228
4247
  .filter-chip-halted.active { background: var(--status-paused); border-color: var(--status-paused); }
4248
+ .filter-chip-integration-failed.active,
4249
+ .filter-chip-integration_failed.active { background: var(--status-failed); border-color: var(--status-failed); }
4229
4250
  .filter-chip .chip-count {
4230
4251
  font-size: 0.68rem;
4231
4252
  opacity: 0.7;
@@ -4341,6 +4362,10 @@ sl-details.learnings-panel::part(content) {
4341
4362
  .pipeline-failed { border-left-color: var(--status-failed); }
4342
4363
  .pipeline-paused { border-left-color: var(--status-paused); }
4343
4364
  .pipeline-cancelled { border-left-color: var(--status-cancelled); }
4365
+ .pipeline-planning { border-left-color: var(--status-running); }
4366
+ .pipeline-integration-testing { border-left-color: var(--status-running); }
4367
+ .pipeline-integration-failed { border-left-color: var(--status-failed); }
4368
+ .pipeline-blocked { border-left-color: var(--status-paused); }
4344
4369
  .pipeline-unknown { border-left-color: var(--muted); }
4345
4370
 
4346
4371
  .pipeline-card-header {
@@ -5708,3 +5733,416 @@ sl-tooltip.bead-tooltip::part(body) {
5708
5733
  .fleet-detail-empty-hint a {
5709
5734
  color: var(--accent, var(--fg));
5710
5735
  }
5736
+
5737
+ /* --- Workspace: tier styling --- */
5738
+
5739
+ .workspace-card-tiers {
5740
+ display: flex;
5741
+ flex-direction: column;
5742
+ gap: 6px;
5743
+ margin-top: 8px;
5744
+ }
5745
+
5746
+ .workspace-tier-row {
5747
+ display: flex;
5748
+ align-items: center;
5749
+ gap: 8px;
5750
+ }
5751
+
5752
+ .tier-label {
5753
+ font-size: 11px;
5754
+ font-weight: 600;
5755
+ color: var(--muted);
5756
+ text-transform: uppercase;
5757
+ letter-spacing: 0.05em;
5758
+ min-width: 60px;
5759
+ flex-shrink: 0;
5760
+ }
5761
+
5762
+ .tier-status {
5763
+ display: inline-flex;
5764
+ align-items: center;
5765
+ flex-shrink: 0;
5766
+ }
5767
+
5768
+ .tier-children {
5769
+ display: flex;
5770
+ flex-wrap: wrap;
5771
+ gap: 4px;
5772
+ }
5773
+
5774
+ .tier-child {
5775
+ font-size: 12px;
5776
+ padding: 2px 8px;
5777
+ border-radius: 4px;
5778
+ background: var(--bg-tertiary);
5779
+ border: 1px solid var(--border);
5780
+ }
5781
+
5782
+ /* --- Workspace: DAG graph --- */
5783
+
5784
+ .dag-preview {
5785
+ overflow-x: auto;
5786
+ border: 1px solid var(--border);
5787
+ border-radius: var(--radius);
5788
+ background: var(--bg-secondary);
5789
+ padding: 16px;
5790
+ }
5791
+
5792
+ .dag-preview svg {
5793
+ display: block;
5794
+ }
5795
+
5796
+ .workspace-dag-panel {
5797
+ display: flex;
5798
+ flex-direction: column;
5799
+ gap: 8px;
5800
+ }
5801
+
5802
+
5803
+
5804
+ .workspace-dag-svg {
5805
+ overflow-x: auto;
5806
+ }
5807
+
5808
+ /* --- Workspace run card --- */
5809
+
5810
+ /* --- Plan stage artifact strip (run-detail) --- */
5811
+
5812
+ .plan-artifact-strip {
5813
+ display: flex;
5814
+ align-items: center;
5815
+ flex-wrap: wrap;
5816
+ gap: 8px;
5817
+ margin: 10px 0 4px;
5818
+ }
5819
+ .plan-artifact-strip .plan-file-chip {
5820
+ font-family: var(--font-mono);
5821
+ font-size: 12px;
5822
+ color: var(--fg-muted);
5823
+ background: var(--bg-secondary);
5824
+ padding: 2px 6px;
5825
+ border-radius: 4px;
5826
+ max-width: 360px;
5827
+ overflow: hidden;
5828
+ text-overflow: ellipsis;
5829
+ white-space: nowrap;
5830
+ }
5831
+ .plan-loading,
5832
+ .plan-error {
5833
+ display: flex;
5834
+ align-items: center;
5835
+ gap: 8px;
5836
+ padding: 20px;
5837
+ color: var(--fg-muted);
5838
+ }
5839
+
5840
+ /* --- sl-dialog: wider markdown-body dialogs (plan, guide) --- */
5841
+
5842
+ sl-dialog.markdown-dialog::part(panel) {
5843
+ /* Default sl-dialog is ~31rem wide — too narrow for prose with code
5844
+ blocks. Cap at 90vw so it stays comfortable on small viewports. */
5845
+ width: min(960px, 90vw);
5846
+ max-width: min(960px, 90vw);
5847
+ }
5848
+ sl-dialog.markdown-dialog::part(body) {
5849
+ /* Give the body breathing room and let it scroll vertically instead
5850
+ of the whole dialog growing without bound. */
5851
+ max-height: 70vh;
5852
+ overflow: auto;
5853
+ }
5854
+
5855
+ /* --- Markdown body styling (used by guide-content + workspace-plan-content) --- */
5856
+
5857
+ .markdown-body {
5858
+ font-family: var(--sl-font-sans, system-ui, sans-serif);
5859
+ font-size: 14px;
5860
+ line-height: 1.55;
5861
+ color: var(--fg);
5862
+ }
5863
+ .markdown-body h1,
5864
+ .markdown-body h2,
5865
+ .markdown-body h3,
5866
+ .markdown-body h4 {
5867
+ margin: 1.2em 0 0.5em;
5868
+ line-height: 1.25;
5869
+ font-weight: 600;
5870
+ }
5871
+ .markdown-body h1 { font-size: 1.5em; }
5872
+ .markdown-body h2 { font-size: 1.25em; border-bottom: 1px solid var(--border-subtle); padding-bottom: 4px; }
5873
+ .markdown-body h3 { font-size: 1.1em; }
5874
+ .markdown-body p { margin: 0.6em 0; }
5875
+ .markdown-body ul,
5876
+ .markdown-body ol { margin: 0.6em 0; padding-left: 1.5em; }
5877
+ .markdown-body li { margin: 0.25em 0; }
5878
+ .markdown-body code {
5879
+ font-family: var(--font-mono);
5880
+ font-size: 0.9em;
5881
+ background: var(--bg-secondary);
5882
+ padding: 1px 5px;
5883
+ border-radius: 3px;
5884
+ }
5885
+ .markdown-body pre {
5886
+ background: var(--bg-secondary);
5887
+ border: 1px solid var(--border-subtle);
5888
+ border-radius: 6px;
5889
+ padding: 10px 12px;
5890
+ overflow-x: auto;
5891
+ font-size: 12.5px;
5892
+ }
5893
+ .markdown-body pre code {
5894
+ background: transparent;
5895
+ padding: 0;
5896
+ border-radius: 0;
5897
+ }
5898
+ .markdown-body blockquote {
5899
+ border-left: 3px solid var(--border);
5900
+ padding: 4px 12px;
5901
+ margin: 0.6em 0;
5902
+ color: var(--fg-muted);
5903
+ }
5904
+ .markdown-body a { color: var(--accent); }
5905
+ .markdown-body table {
5906
+ border-collapse: collapse;
5907
+ margin: 0.8em 0;
5908
+ }
5909
+ .markdown-body th,
5910
+ .markdown-body td {
5911
+ border: 1px solid var(--border-subtle);
5912
+ padding: 4px 8px;
5913
+ text-align: left;
5914
+ }
5915
+
5916
+ .workspace-run-card .workspace-card-root,
5917
+ .workspace-run-card .workspace-card-name {
5918
+ font-family: var(--font-mono);
5919
+ font-size: 12px;
5920
+ color: var(--fg-muted);
5921
+ }
5922
+
5923
+ /* --- Workspace forms: external-repo picker --- */
5924
+
5925
+ .repo-checklist-actions {
5926
+ display: flex;
5927
+ align-items: center;
5928
+ gap: 12px;
5929
+ margin-top: 12px;
5930
+ flex-wrap: wrap;
5931
+ }
5932
+ .ws-external-tag {
5933
+ margin-left: 8px;
5934
+ }
5935
+
5936
+ /* --- Workspace forms: Dependency Graph label --- */
5937
+
5938
+ .dag-preview-block {
5939
+ margin-top: 16px;
5940
+ }
5941
+ .dag-preview-label {
5942
+ display: block;
5943
+ margin-bottom: 8px;
5944
+ }
5945
+
5946
+ /* --- Workspace edit: scan-for-additions status + Available tag --- */
5947
+
5948
+ .ws-edit-scan-status {
5949
+ display: flex;
5950
+ align-items: center;
5951
+ gap: 8px;
5952
+ font-size: 13px;
5953
+ color: var(--fg-muted);
5954
+ margin-bottom: 12px;
5955
+ }
5956
+ .ws-edit-scan-error {
5957
+ margin-bottom: 12px;
5958
+ }
5959
+ .ws-edit-new-tag {
5960
+ margin-left: 8px;
5961
+ }
5962
+
5963
+ /* --- Workspace create: Parent Directory row --- */
5964
+
5965
+ .parent-dir-row {
5966
+ display: flex;
5967
+ gap: 8px;
5968
+ align-items: stretch;
5969
+ }
5970
+ .parent-dir-row .input-parent-dir {
5971
+ flex: 1;
5972
+ }
5973
+ .parent-dir-suggestions {
5974
+ margin-top: 8px;
5975
+ border: 1px solid var(--border);
5976
+ border-radius: 6px;
5977
+ background: var(--bg);
5978
+ overflow: hidden;
5979
+ }
5980
+ .parent-dir-suggestions-label {
5981
+ padding: 6px 12px;
5982
+ font-size: 11px;
5983
+ text-transform: uppercase;
5984
+ letter-spacing: 0.04em;
5985
+ color: var(--fg-muted);
5986
+ background: var(--bg-secondary);
5987
+ border-bottom: 1px solid var(--border-subtle);
5988
+ }
5989
+ .parent-dir-suggestion-item {
5990
+ display: flex;
5991
+ align-items: center;
5992
+ gap: 8px;
5993
+ width: 100%;
5994
+ padding: 8px 12px;
5995
+ background: transparent;
5996
+ border: none;
5997
+ border-bottom: 1px solid var(--border-subtle);
5998
+ cursor: pointer;
5999
+ font-family: var(--font-mono);
6000
+ font-size: 13px;
6001
+ color: var(--fg);
6002
+ text-align: left;
6003
+ }
6004
+ .parent-dir-suggestion-item:last-child {
6005
+ border-bottom: none;
6006
+ }
6007
+ .parent-dir-suggestion-item:hover {
6008
+ background: var(--bg-hover);
6009
+ }
6010
+ .parent-dir-suggestion-item svg {
6011
+ flex-shrink: 0;
6012
+ color: var(--fg-muted);
6013
+ }
6014
+
6015
+ /* --- Configuration → Workspaces list --- */
6016
+
6017
+ .workspaces-config-empty {
6018
+ display: flex;
6019
+ flex-direction: column;
6020
+ align-items: center;
6021
+ justify-content: center;
6022
+ text-align: center;
6023
+ padding: 64px 24px;
6024
+ gap: 12px;
6025
+ color: var(--fg-muted);
6026
+ }
6027
+ .workspaces-config-empty .empty-icon {
6028
+ color: var(--fg-muted);
6029
+ opacity: 0.5;
6030
+ margin-bottom: 8px;
6031
+ }
6032
+ .workspaces-config-empty h3 {
6033
+ margin: 0;
6034
+ color: var(--fg);
6035
+ }
6036
+ .workspaces-config-empty p {
6037
+ max-width: 480px;
6038
+ margin: 0 0 12px;
6039
+ line-height: 1.5;
6040
+ }
6041
+
6042
+ .workspaces-config-table {
6043
+ padding: 16px;
6044
+ overflow-x: auto;
6045
+ }
6046
+ .workspaces-config-table table {
6047
+ width: 100%;
6048
+ border-collapse: collapse;
6049
+ font-size: 14px;
6050
+ }
6051
+ .workspaces-config-table thead th {
6052
+ text-align: left;
6053
+ font-weight: 600;
6054
+ padding: 8px 12px;
6055
+ border-bottom: 1px solid var(--border);
6056
+ color: var(--fg-muted);
6057
+ text-transform: uppercase;
6058
+ font-size: 11px;
6059
+ letter-spacing: 0.04em;
6060
+ }
6061
+ .workspaces-config-table tbody td {
6062
+ padding: 12px;
6063
+ border-bottom: 1px solid var(--border);
6064
+ vertical-align: middle;
6065
+ }
6066
+ .workspaces-config-table tbody tr:hover {
6067
+ background: var(--bg-hover);
6068
+ }
6069
+ .workspaces-config-table .ws-name strong {
6070
+ font-weight: 600;
6071
+ }
6072
+ .workspaces-config-table .ws-path code,
6073
+ .workspaces-config-table .ws-umbrella {
6074
+ font-family: var(--font-mono);
6075
+ font-size: 12px;
6076
+ color: var(--fg-muted);
6077
+ }
6078
+ .workspaces-config-table .ws-tiers {
6079
+ color: var(--fg-muted);
6080
+ font-size: 12px;
6081
+ margin-left: 4px;
6082
+ }
6083
+ .workspaces-config-table .ws-dash {
6084
+ color: var(--fg-muted);
6085
+ opacity: 0.5;
6086
+ }
6087
+ .workspaces-config-table .actions-col {
6088
+ text-align: right;
6089
+ }
6090
+ .workspaces-config-table .ws-actions {
6091
+ display: flex;
6092
+ gap: 4px;
6093
+ justify-content: flex-end;
6094
+ }
6095
+ .ws-action-btn {
6096
+ background: transparent;
6097
+ border: 1px solid var(--border);
6098
+ border-radius: 4px;
6099
+ padding: 4px 6px;
6100
+ cursor: pointer;
6101
+ color: var(--fg);
6102
+ display: inline-flex;
6103
+ align-items: center;
6104
+ justify-content: center;
6105
+ }
6106
+ .ws-action-btn:hover {
6107
+ background: var(--bg-hover);
6108
+ }
6109
+ .ws-action-btn--danger:hover {
6110
+ background: rgba(220, 38, 38, 0.1);
6111
+ color: rgb(220, 38, 38);
6112
+ border-color: rgba(220, 38, 38, 0.4);
6113
+ }
6114
+
6115
+ .dag-graph-node rect {
6116
+ rx: 6;
6117
+ fill: var(--bg);
6118
+ stroke: var(--border);
6119
+ stroke-width: 1.5;
6120
+ }
6121
+
6122
+ .dag-graph-node text {
6123
+ font-size: 12px;
6124
+ font-family: var(--sl-font-sans);
6125
+ fill: var(--fg);
6126
+ }
6127
+
6128
+ .dag-graph-edge {
6129
+ fill: none;
6130
+ stroke: var(--border);
6131
+ stroke-width: 2;
6132
+ }
6133
+
6134
+ .dag-graph-node--status-pending rect { stroke: var(--status-pending); }
6135
+ .dag-graph-node--status-running rect { stroke: var(--status-running); }
6136
+ .dag-graph-node--status-completed rect { stroke: var(--status-completed); }
6137
+ .dag-graph-node--status-failed rect { stroke: var(--status-failed); }
6138
+ .dag-graph-node--status-paused rect { stroke: var(--status-paused); }
6139
+ .dag-graph-node--status-blocked rect { stroke: var(--status-blocked); }
6140
+ .dag-graph-node--status-planning rect { stroke: var(--status-running); }
6141
+ .dag-graph-node--status-integration-testing rect { stroke: var(--status-running); }
6142
+ .dag-graph-node--status-integration-failed rect { stroke: var(--status-failed); }
6143
+
6144
+ /* --- Workspace: conflict icon --- */
6145
+
6146
+ .conflict-icon {
6147
+ color: var(--status-blocked);
6148
+ }
@@ -9,12 +9,23 @@ export const STATES = [
9
9
  'halted',
10
10
  'setup_failed',
11
11
  'unrecoverable',
12
+ 'planning',
13
+ 'integration_testing',
14
+ 'integration_failed',
15
+ 'blocked',
12
16
  ];
13
17
 
14
18
  const ACTION_MATRIX = {
15
- stop: { running: true },
16
- pause: { running: true },
17
- resume: { paused: true, failed: true, interrupted: true, halted: true },
19
+ stop: { running: true, planning: true, integration_testing: true },
20
+ pause: { running: true, planning: true, integration_testing: true },
21
+ resume: {
22
+ paused: true,
23
+ failed: true,
24
+ interrupted: true,
25
+ halted: true,
26
+ integration_failed: true,
27
+ blocked: true,
28
+ },
18
29
  cancel: {
19
30
  pending: true,
20
31
  running: true,
@@ -23,6 +34,8 @@ const ACTION_MATRIX = {
23
34
  interrupted: true,
24
35
  halted: true,
25
36
  setup_failed: true,
37
+ integration_failed: true,
38
+ blocked: true,
26
39
  },
27
40
  archive: {
28
41
  paused: true,
@@ -33,6 +46,7 @@ const ACTION_MATRIX = {
33
46
  halted: true,
34
47
  setup_failed: true,
35
48
  unrecoverable: true,
49
+ integration_failed: true,
36
50
  },
37
51
  unarchive: {
38
52
  completed: true,
@@ -50,6 +64,8 @@ const ACTION_MATRIX = {
50
64
  halted: true,
51
65
  setup_failed: true,
52
66
  unrecoverable: true,
67
+ integration_failed: true,
68
+ blocked: true,
53
69
  },
54
70
  learn: {
55
71
  paused: true,
@@ -58,6 +74,8 @@ const ACTION_MATRIX = {
58
74
  interrupted: true,
59
75
  cancelled: true,
60
76
  halted: true,
77
+ integration_failed: true,
78
+ blocked: true,
61
79
  },
62
80
  };
63
81
 
@@ -0,0 +1,11 @@
1
+ // AUTO-GENERATED by scripts/build-frontend.js — do not edit.
2
+ // Source: src/worca/state/status.py
3
+
4
+ export const FLEET_STICKY = Object.freeze(new Set(["halted","paused"]));
5
+ export const FLEET_TERMINAL = Object.freeze(new Set(["completed","failed","halted"]));
6
+ export const WORKSPACE_TERMINAL = Object.freeze(new Set(["completed","failed","halted","integration_failed"]));
7
+ export const PIPELINE_ACTIVE = Object.freeze(new Set(["paused","resuming","running"]));
8
+ export const PIPELINE_TERMINAL = Object.freeze(new Set(["completed","interrupted"]));
9
+ export const PIPELINE_FAILURE = Object.freeze(new Set(["failed","setup_failed","unrecoverable"]));
10
+ export const PIPELINE_ALL_TERMINAL = Object.freeze(new Set(["cancelled","completed","failed","interrupted","setup_failed","unrecoverable"]));
11
+ export const PIPELINE_IN_FLIGHT = Object.freeze(new Set(["resuming","running"]));
package/bin/worca-ui.js CHANGED
@@ -12,9 +12,9 @@ import {
12
12
  writeFileSync,
13
13
  } from 'node:fs';
14
14
  import { connect, createServer } from 'node:net';
15
- import { homedir } from 'node:os';
16
15
  import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
17
16
  import { fileURLToPath } from 'node:url';
17
+ import { worcaHome } from '../server/paths.js';
18
18
  import {
19
19
  readProjects,
20
20
  removeProject,
@@ -37,7 +37,7 @@ function findProjectRoot(startDir) {
37
37
  return startDir;
38
38
  }
39
39
 
40
- const PREFS_DIR = join(homedir(), '.worca');
40
+ const PREFS_DIR = worcaHome();
41
41
  const SERVER_SCRIPT = join(__dirname, '..', 'server', 'index.js');
42
42
 
43
43
  /** Exported for testing */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -38,6 +38,7 @@
38
38
  "app/protocol.js",
39
39
  "app/utils/stage-order.js",
40
40
  "app/utils/state-actions.js",
41
+ "app/utils/status-constants.js",
41
42
  "scripts/build-frontend.js"
42
43
  ],
43
44
  "engines": {
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { copyFileSync, mkdirSync } from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { copyFileSync, mkdirSync, writeFileSync } from 'node:fs';
3
4
  import path from 'node:path';
4
5
  import { fileURLToPath } from 'node:url';
5
6
 
@@ -41,6 +42,52 @@ async function run() {
41
42
  console.log('copied', path.relative(repoRoot, dest));
42
43
  }
43
44
 
45
+ // Codegen: derive status-constants.js from the Python enums in
46
+ // src/worca/state/status.py. Single source of truth — when Python adds a
47
+ // status, the JS side picks it up at the next build. Shipped in the npm
48
+ // package via the `files` allowlist in package.json.
49
+ const utilsDir = path.join(appDir, 'utils');
50
+ mkdirSync(utilsDir, { recursive: true });
51
+ const constantsOut = path.join(utilsDir, 'status-constants.js');
52
+ try {
53
+ const pyScript =
54
+ 'import json; from worca.state import status as s; ' +
55
+ 'print(json.dumps({' +
56
+ '"FLEET_STICKY": sorted(s.FLEET_STICKY), ' +
57
+ '"FLEET_TERMINAL": sorted(s.FLEET_TERMINAL), ' +
58
+ '"WORKSPACE_TERMINAL": sorted(s.WORKSPACE_TERMINAL), ' +
59
+ '"PIPELINE_ACTIVE": sorted(s.PIPELINE_ACTIVE), ' +
60
+ '"PIPELINE_TERMINAL": sorted(s.PIPELINE_TERMINAL), ' +
61
+ '"PIPELINE_FAILURE": sorted(s.PIPELINE_FAILURE), ' +
62
+ '"PIPELINE_ALL_TERMINAL": sorted(s.PIPELINE_ALL_TERMINAL), ' +
63
+ '"PIPELINE_IN_FLIGHT": sorted(s.PIPELINE_IN_FLIGHT)' +
64
+ '}))';
65
+ const raw = execFileSync('python3', ['-c', pyScript], {
66
+ cwd: path.join(repoRoot, '..'),
67
+ encoding: 'utf8',
68
+ });
69
+ const constants = JSON.parse(raw);
70
+ const lines = [
71
+ '// AUTO-GENERATED by scripts/build-frontend.js — do not edit.',
72
+ '// Source: src/worca/state/status.py',
73
+ '',
74
+ ];
75
+ for (const [name, values] of Object.entries(constants)) {
76
+ lines.push(
77
+ `export const ${name} = Object.freeze(new Set(${JSON.stringify(values)}));`,
78
+ );
79
+ }
80
+ writeFileSync(constantsOut, `${lines.join('\n')}\n`);
81
+ console.log('generated', path.relative(repoRoot, constantsOut));
82
+ } catch (err) {
83
+ console.error(
84
+ 'status-constants codegen failed (is python3 + worca-cc installed?):',
85
+ err.message,
86
+ );
87
+ process.exitCode = 1;
88
+ return;
89
+ }
90
+
44
91
  try {
45
92
  const esbuild = await import('esbuild');
46
93
  await esbuild.build({