prompt_objects 0.4.0 → 0.6.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/CLAUDE.md +113 -44
  4. data/README.md +140 -14
  5. data/frontend/index.html +5 -1
  6. data/frontend/src/App.tsx +72 -79
  7. data/frontend/src/canvas/CanvasView.tsx +5 -5
  8. data/frontend/src/canvas/constants.ts +31 -31
  9. data/frontend/src/canvas/inspector/InspectorPanel.tsx +4 -4
  10. data/frontend/src/canvas/inspector/POInspector.tsx +35 -35
  11. data/frontend/src/canvas/inspector/ToolCallInspector.tsx +13 -13
  12. data/frontend/src/canvas/nodes/PONode.ts +2 -2
  13. data/frontend/src/components/ContextMenu.tsx +5 -4
  14. data/frontend/src/components/EnvDataPane.tsx +69 -0
  15. data/frontend/src/components/Inspector.tsx +263 -0
  16. data/frontend/src/components/MarkdownMessage.tsx +22 -20
  17. data/frontend/src/components/MethodList.tsx +90 -0
  18. data/frontend/src/components/ModelSelector.tsx +13 -14
  19. data/frontend/src/components/NotificationPanel.tsx +29 -33
  20. data/frontend/src/components/ObjectList.tsx +78 -0
  21. data/frontend/src/components/PaneSlot.tsx +30 -0
  22. data/frontend/src/components/SourcePane.tsx +202 -0
  23. data/frontend/src/components/SystemBar.tsx +74 -0
  24. data/frontend/src/components/Transcript.tsx +76 -0
  25. data/frontend/src/components/UsagePanel.tsx +27 -27
  26. data/frontend/src/components/Workspace.tsx +260 -0
  27. data/frontend/src/components/index.ts +10 -9
  28. data/frontend/src/hooks/useResize.ts +55 -0
  29. data/frontend/src/hooks/useWebSocket.ts +70 -0
  30. data/frontend/src/index.css +27 -10
  31. data/frontend/src/store/index.ts +36 -0
  32. data/frontend/src/types/index.ts +13 -0
  33. data/frontend/tailwind.config.js +28 -9
  34. data/lib/prompt_objects/capability.rb +23 -1
  35. data/lib/prompt_objects/connectors/mcp.rb +2 -16
  36. data/lib/prompt_objects/environment.rb +15 -0
  37. data/lib/prompt_objects/llm/openai_adapter.rb +22 -0
  38. data/lib/prompt_objects/mcp/tools/inspect_po.rb +1 -31
  39. data/lib/prompt_objects/mcp/tools/list_prompt_objects.rb +1 -6
  40. data/lib/prompt_objects/prompt_object.rb +239 -7
  41. data/lib/prompt_objects/server/api/routes.rb +16 -48
  42. data/lib/prompt_objects/server/app.rb +14 -0
  43. data/lib/prompt_objects/server/public/assets/{index-xvyeb-5Z.js → index-DEPawnfZ.js} +206 -206
  44. data/lib/prompt_objects/server/public/assets/index-oMrRce1m.css +1 -0
  45. data/lib/prompt_objects/server/public/index.html +7 -3
  46. data/lib/prompt_objects/server/websocket_handler.rb +41 -98
  47. data/lib/prompt_objects/server.rb +6 -62
  48. data/lib/prompt_objects/session/store.rb +176 -4
  49. data/lib/prompt_objects/universal/delete_env_data.rb +70 -0
  50. data/lib/prompt_objects/universal/get_env_data.rb +64 -0
  51. data/lib/prompt_objects/universal/list_env_data.rb +61 -0
  52. data/lib/prompt_objects/universal/store_env_data.rb +87 -0
  53. data/lib/prompt_objects/universal/update_env_data.rb +88 -0
  54. data/lib/prompt_objects.rb +6 -1
  55. data/prompt_objects.gemspec +1 -1
  56. data/templates/arc-agi-1/objects/observer.md +4 -0
  57. data/templates/arc-agi-1/objects/solver.md +10 -1
  58. data/templates/arc-agi-1/objects/verifier.md +4 -0
  59. data/templates/arc-agi-1/primitives/find_objects.rb +1 -1
  60. data/templates/arc-agi-1/primitives/grid_diff.rb +2 -2
  61. data/templates/arc-agi-1/primitives/grid_info.rb +1 -1
  62. data/templates/arc-agi-1/primitives/grid_transform.rb +1 -1
  63. data/templates/arc-agi-1/primitives/render_grid.rb +1 -0
  64. data/templates/arc-agi-1/primitives/test_solution.rb +3 -0
  65. data/tools/thread-explorer.html +27 -0
  66. metadata +18 -16
  67. data/Gemfile.lock +0 -233
  68. data/IMPLEMENTATION_PLAN.md +0 -1073
  69. data/design-doc-v2.md +0 -1232
  70. data/frontend/src/components/CapabilitiesPanel.tsx +0 -141
  71. data/frontend/src/components/ChatPanel.tsx +0 -296
  72. data/frontend/src/components/Dashboard.tsx +0 -83
  73. data/frontend/src/components/Header.tsx +0 -153
  74. data/frontend/src/components/MessageBus.tsx +0 -56
  75. data/frontend/src/components/POCard.tsx +0 -56
  76. data/frontend/src/components/PODetail.tsx +0 -124
  77. data/frontend/src/components/PromptPanel.tsx +0 -156
  78. data/frontend/src/components/SessionsPanel.tsx +0 -174
  79. data/frontend/src/components/ThreadsSidebar.tsx +0 -163
  80. data/lib/prompt_objects/server/public/assets/index-6y64NXFy.css +0 -1
@@ -1,46 +1,46 @@
1
1
  // Canvas visualization constants
2
2
 
3
- // Colors (hex values matching po-* palette)
3
+ // Colors (hex values matching warm po-* palette)
4
4
  export const COLORS = {
5
5
  // Node colors
6
- background: 0x0f0f1a,
7
- surface: 0x1a1a2e,
8
- border: 0x2d2d44,
9
- accent: 0x7c3aed,
10
- accentHover: 0x9061f9,
11
- success: 0x22c55e,
12
- warning: 0xf59e0b,
13
- error: 0xef4444,
6
+ background: 0x1a1918,
7
+ surface: 0x222120,
8
+ border: 0x3d3a37,
9
+ accent: 0xd4952a,
10
+ accentHover: 0xe0a940,
11
+ success: 0x3b9a6e,
12
+ warning: 0xd4952a,
13
+ error: 0xc45c4a,
14
14
 
15
15
  // Status colors
16
- statusIdle: 0x6b7280,
17
- statusThinking: 0x7c3aed,
18
- statusCallingTool: 0xf59e0b,
16
+ statusIdle: 0x78726a,
17
+ statusThinking: 0xd4952a,
18
+ statusCallingTool: 0x3b9a6e,
19
19
 
20
20
  // Canvas-specific
21
- nodeFill: 0x1a1a2e,
22
- nodeGlow: 0x7c3aed,
23
- toolCallFill: 0x3b82f6,
24
- arcColor: 0x7c3aed,
25
- particleColor: 0xc084fc,
26
- gridColor: 0x1a1a2e,
21
+ nodeFill: 0x222120,
22
+ nodeGlow: 0xd4952a,
23
+ toolCallFill: 0x3b9a6e,
24
+ arcColor: 0xd4952a,
25
+ particleColor: 0xe0a940,
26
+ gridColor: 0x222120,
27
27
  } as const
28
28
 
29
29
  // CSS color strings (for CSS2DRenderer elements)
30
30
  export const CSS_COLORS = {
31
- accent: '#7c3aed',
32
- accentHover: '#9061f9',
33
- warning: '#f59e0b',
34
- success: '#22c55e',
35
- error: '#ef4444',
36
- textPrimary: '#ffffff',
37
- textSecondary: '#9ca3af',
38
- textMuted: '#6b7280',
39
- surface: '#1a1a2e',
40
- border: '#2d2d44',
41
- statusIdle: '#6b7280',
42
- statusThinking: '#7c3aed',
43
- statusCallingTool: '#f59e0b',
31
+ accent: '#d4952a',
32
+ accentHover: '#e0a940',
33
+ warning: '#d4952a',
34
+ success: '#3b9a6e',
35
+ error: '#c45c4a',
36
+ textPrimary: '#e8e2da',
37
+ textSecondary: '#a8a29a',
38
+ textMuted: '#78726a',
39
+ surface: '#222120',
40
+ border: '#3d3a37',
41
+ statusIdle: '#78726a',
42
+ statusThinking: '#d4952a',
43
+ statusCallingTool: '#3b9a6e',
44
44
  } as const
45
45
 
46
46
  // Node dimensions
@@ -9,14 +9,14 @@ export function InspectorPanel() {
9
9
 
10
10
  return (
11
11
  <aside className="w-80 border-l border-po-border bg-po-surface overflow-hidden flex flex-col">
12
- <div className="p-3 border-b border-po-border flex items-center justify-between">
13
- <h2 className="text-sm font-medium text-gray-400">Inspector</h2>
12
+ <div className="h-8 bg-po-surface-2 border-b border-po-border flex items-center px-3">
13
+ <span className="text-2xs font-medium text-po-text-ghost uppercase tracking-wider flex-1">Inspector</span>
14
14
  <button
15
15
  onClick={() => useCanvasStore.getState().selectNode(null)}
16
- className="text-xs text-gray-500 hover:text-white"
16
+ className="text-2xs text-po-text-ghost hover:text-po-text-secondary transition-colors duration-150"
17
17
  title="Close inspector"
18
18
  >
19
-
19
+ {'\u2715'}
20
20
  </button>
21
21
  </div>
22
22
  <div className="flex-1 overflow-auto">
@@ -7,9 +7,9 @@ interface Props {
7
7
  }
8
8
 
9
9
  const statusColors: Record<string, string> = {
10
- idle: 'bg-gray-500',
11
- thinking: 'bg-po-accent animate-pulse',
12
- calling_tool: 'bg-po-warning animate-pulse',
10
+ idle: 'bg-po-status-idle',
11
+ thinking: 'bg-po-status-active animate-pulse',
12
+ calling_tool: 'bg-po-status-calling animate-pulse',
13
13
  }
14
14
 
15
15
  export function POInspector({ poName }: Props) {
@@ -20,7 +20,7 @@ export function POInspector({ poName }: Props) {
20
20
 
21
21
  if (!po) {
22
22
  return (
23
- <div className="p-4 text-gray-500 text-sm">
23
+ <div className="p-4 text-po-text-ghost text-xs font-mono">
24
24
  Prompt Object "{poName}" not found.
25
25
  </div>
26
26
  )
@@ -31,18 +31,18 @@ export function POInspector({ poName }: Props) {
31
31
  {/* Header */}
32
32
  <div>
33
33
  <div className="flex items-center gap-2 mb-1">
34
- <h3 className="text-lg font-medium text-white">{po.name}</h3>
34
+ <h3 className="text-sm font-mono font-medium text-po-text-primary">{po.name}</h3>
35
35
  <div className={`w-2 h-2 rounded-full ${statusColors[po.status] || statusColors.idle}`} />
36
36
  </div>
37
- <p className="text-sm text-gray-400">{po.description}</p>
38
- <span className="inline-block mt-1 text-xs text-gray-500 bg-po-bg px-2 py-0.5 rounded">
37
+ <p className="text-xs text-po-text-secondary">{po.description}</p>
38
+ <span className="inline-block mt-1 text-2xs text-po-text-ghost bg-po-surface-2 px-1.5 py-0.5 rounded font-mono">
39
39
  {po.status}
40
40
  </span>
41
41
  </div>
42
42
 
43
43
  {/* Capabilities */}
44
44
  <div>
45
- <h4 className="text-sm font-medium text-gray-400 mb-2">
45
+ <h4 className="text-2xs font-medium text-po-text-ghost uppercase tracking-wider mb-2">
46
46
  Capabilities ({po.capabilities?.length || 0})
47
47
  </h4>
48
48
  <div className="space-y-1">
@@ -60,12 +60,12 @@ export function POInspector({ poName }: Props) {
60
60
 
61
61
  {/* Sessions */}
62
62
  <div>
63
- <h4 className="text-sm font-medium text-gray-400 mb-2">
63
+ <h4 className="text-2xs font-medium text-po-text-ghost uppercase tracking-wider mb-2">
64
64
  Sessions ({po.sessions?.length || 0})
65
65
  </h4>
66
66
  {po.current_session && (
67
- <div className="text-xs text-gray-500">
68
- Current: <span className="text-gray-300 font-mono">{po.current_session.id.slice(0, 8)}...</span>
67
+ <div className="text-xs text-po-text-tertiary">
68
+ Current: <span className="text-po-text-secondary font-mono">{po.current_session.id.slice(0, 8)}...</span>
69
69
  <span className="ml-2">({po.current_session.messages.length} messages)</span>
70
70
  </div>
71
71
  )}
@@ -74,7 +74,7 @@ export function POInspector({ poName }: Props) {
74
74
  {/* Notifications */}
75
75
  {notifications.length > 0 && (
76
76
  <div>
77
- <h4 className="text-sm font-medium text-po-warning mb-2">
77
+ <h4 className="text-2xs font-medium text-po-warning uppercase tracking-wider mb-2">
78
78
  Pending Requests ({notifications.length})
79
79
  </h4>
80
80
  <div className="space-y-2">
@@ -95,9 +95,9 @@ export function POInspector({ poName }: Props) {
95
95
  selectPO(poName)
96
96
  setCurrentView('dashboard')
97
97
  }}
98
- className="text-sm text-po-accent hover:underline"
98
+ className="text-xs text-po-accent hover:underline font-mono transition-colors duration-150"
99
99
  >
100
- Open full detail view
100
+ Open in browser view
101
101
  </button>
102
102
  </div>
103
103
  )
@@ -107,17 +107,17 @@ function CapabilityItem({ name, description }: { name: string; description: stri
107
107
  const [expanded, setExpanded] = useState(false)
108
108
 
109
109
  return (
110
- <div className="bg-po-bg border border-po-border rounded overflow-hidden">
110
+ <div className="bg-po-surface-2 border border-po-border rounded overflow-hidden">
111
111
  <button
112
112
  onClick={() => setExpanded(!expanded)}
113
- className="w-full px-3 py-1.5 flex items-center justify-between hover:bg-po-surface transition-colors"
113
+ className="w-full px-2.5 py-1.5 flex items-center justify-between hover:bg-po-surface-3 transition-colors duration-150"
114
114
  >
115
115
  <span className="font-mono text-xs text-po-accent">{name}</span>
116
- <span className="text-gray-500 text-xs">{expanded ? '' : ''}</span>
116
+ <span className="text-po-text-ghost text-xs">{expanded ? '\u25BC' : '\u25B8'}</span>
117
117
  </button>
118
118
  {expanded && (
119
- <div className="px-3 py-2 border-t border-po-border bg-po-surface">
120
- <p className="text-xs text-gray-400">{description}</p>
119
+ <div className="px-2.5 py-2 border-t border-po-border bg-po-surface">
120
+ <p className="text-xs text-po-text-secondary">{description}</p>
121
121
  </div>
122
122
  )}
123
123
  </div>
@@ -152,16 +152,16 @@ function PromptSection({ prompt, onSave }: { prompt: string; onSave: (p: string)
152
152
  return (
153
153
  <div>
154
154
  <div className="flex items-center justify-between mb-2">
155
- <h4 className="text-sm font-medium text-gray-400">Prompt</h4>
155
+ <h4 className="text-2xs font-medium text-po-text-ghost uppercase tracking-wider">Prompt</h4>
156
156
  <button
157
157
  onClick={() => {
158
158
  if (isEditing && edited !== prompt) onSave(edited)
159
159
  setIsEditing(!isEditing)
160
160
  }}
161
- className={`text-xs px-2 py-0.5 rounded transition-colors ${
161
+ className={`text-2xs px-1.5 py-0.5 rounded transition-colors duration-150 ${
162
162
  isEditing
163
- ? 'bg-po-accent text-white'
164
- : 'bg-po-border text-gray-300 hover:text-white'
163
+ ? 'bg-po-accent text-po-bg'
164
+ : 'text-po-text-tertiary hover:text-po-text-primary hover:bg-po-surface-2'
165
165
  }`}
166
166
  >
167
167
  {isEditing ? 'Done' : 'Edit'}
@@ -174,12 +174,12 @@ function PromptSection({ prompt, onSave }: { prompt: string; onSave: (p: string)
174
174
  setEdited(e.target.value)
175
175
  debouncedSave(e.target.value)
176
176
  }}
177
- className="w-full h-40 bg-po-bg border border-po-border rounded p-2 text-xs text-gray-200 font-mono resize-none focus:outline-none focus:border-po-accent"
177
+ className="w-full h-40 bg-po-bg border border-po-border rounded p-2 text-xs text-po-text-primary font-mono resize-none focus:outline-none focus:border-po-accent"
178
178
  spellCheck={false}
179
179
  />
180
180
  ) : (
181
- <div className="bg-po-bg border border-po-border rounded p-2 max-h-32 overflow-auto">
182
- <pre className="text-xs text-gray-400 font-mono whitespace-pre-wrap">
181
+ <div className="bg-po-surface-2 border border-po-border rounded p-2 max-h-32 overflow-auto">
182
+ <pre className="text-xs text-po-text-secondary font-mono whitespace-pre-wrap">
183
183
  {prompt || '(no prompt)'}
184
184
  </pre>
185
185
  </div>
@@ -199,19 +199,19 @@ function NotificationCard({
199
199
  const [showCustom, setShowCustom] = useState(false)
200
200
 
201
201
  return (
202
- <div className="bg-po-bg border border-po-border rounded p-2">
203
- <span className="text-xs bg-po-warning text-black px-1.5 py-0.5 rounded font-medium">
202
+ <div className="bg-po-surface-2 border border-po-border rounded p-2">
203
+ <span className="text-2xs font-mono bg-po-warning text-po-bg px-1.5 py-0.5 rounded font-bold">
204
204
  {notification.type}
205
205
  </span>
206
- <p className="text-xs text-gray-300 mt-1 mb-2">{notification.message}</p>
206
+ <p className="text-xs text-po-text-primary mt-1.5 mb-2">{notification.message}</p>
207
207
 
208
208
  {notification.options.length > 0 && (
209
- <div className="flex flex-wrap gap-1 mb-1">
209
+ <div className="flex flex-wrap gap-1.5 mb-1.5">
210
210
  {notification.options.map((opt, i) => (
211
211
  <button
212
212
  key={i}
213
213
  onClick={() => onRespond(opt)}
214
- className="px-2 py-1 text-xs bg-po-surface border border-po-border rounded hover:border-po-accent transition-colors"
214
+ className="px-2 py-0.5 text-xs bg-po-surface border border-po-border rounded hover:border-po-accent hover:text-po-accent transition-colors duration-150 text-po-text-secondary"
215
215
  >
216
216
  {opt}
217
217
  </button>
@@ -220,13 +220,13 @@ function NotificationCard({
220
220
  )}
221
221
 
222
222
  {showCustom ? (
223
- <div className="flex gap-1 mt-1">
223
+ <div className="flex gap-1.5 mt-1.5">
224
224
  <input
225
225
  type="text"
226
226
  value={customInput}
227
227
  onChange={(e) => setCustomInput(e.target.value)}
228
228
  placeholder="Custom response..."
229
- className="flex-1 bg-po-surface border border-po-border rounded px-2 py-1 text-xs text-white placeholder-gray-500 focus:outline-none focus:border-po-accent"
229
+ className="flex-1 bg-po-bg border border-po-border rounded px-2 py-1 text-xs text-po-text-primary placeholder-po-text-ghost focus:outline-none focus:border-po-accent"
230
230
  onKeyDown={(e) => {
231
231
  if (e.key === 'Enter' && customInput.trim()) {
232
232
  onRespond(customInput.trim())
@@ -244,7 +244,7 @@ function NotificationCard({
244
244
  setShowCustom(false)
245
245
  }
246
246
  }}
247
- className="px-2 py-1 text-xs bg-po-accent text-white rounded"
247
+ className="px-2 py-1 text-xs bg-po-accent text-po-bg rounded font-medium"
248
248
  >
249
249
  Send
250
250
  </button>
@@ -252,7 +252,7 @@ function NotificationCard({
252
252
  ) : (
253
253
  <button
254
254
  onClick={() => setShowCustom(true)}
255
- className="text-xs text-gray-500 hover:text-white mt-1"
255
+ className="text-2xs text-po-text-ghost hover:text-po-text-secondary transition-colors duration-150 mt-1"
256
256
  >
257
257
  + Custom
258
258
  </button>
@@ -7,7 +7,7 @@ interface Props {
7
7
  const statusColors: Record<string, string> = {
8
8
  active: 'text-po-accent',
9
9
  completed: 'text-po-success',
10
- error: 'text-red-400',
10
+ error: 'text-po-error',
11
11
  }
12
12
 
13
13
  export function ToolCallInspector({ toolCallId }: Props) {
@@ -15,7 +15,7 @@ export function ToolCallInspector({ toolCallId }: Props) {
15
15
 
16
16
  if (!toolCall) {
17
17
  return (
18
- <div className="p-4 text-gray-500 text-sm">
18
+ <div className="p-4 text-po-text-ghost text-xs font-mono">
19
19
  Tool call not found or has expired.
20
20
  </div>
21
21
  )
@@ -29,23 +29,23 @@ export function ToolCallInspector({ toolCallId }: Props) {
29
29
  <div className="p-4 space-y-4">
30
30
  {/* Header */}
31
31
  <div>
32
- <h3 className="text-lg font-medium text-white font-mono">{toolCall.toolName}</h3>
32
+ <h3 className="text-sm font-mono font-medium text-po-text-primary">{toolCall.toolName}</h3>
33
33
  <div className="flex items-center gap-2 mt-1">
34
- <span className={`text-xs font-medium ${statusColors[toolCall.status] || ''}`}>
34
+ <span className={`text-xs font-mono font-medium ${statusColors[toolCall.status] || ''}`}>
35
35
  {toolCall.status}
36
36
  </span>
37
- <span className="text-xs text-gray-500">{duration}</span>
37
+ <span className="text-2xs text-po-text-ghost font-mono">{duration}</span>
38
38
  </div>
39
- <div className="text-xs text-gray-400 mt-1">
40
- Called by: <span className="text-po-accent">{toolCall.callerPO}</span>
39
+ <div className="text-xs text-po-text-tertiary mt-1">
40
+ Called by: <span className="text-po-accent font-mono">{toolCall.callerPO}</span>
41
41
  </div>
42
42
  </div>
43
43
 
44
44
  {/* Parameters */}
45
45
  <div>
46
- <h4 className="text-sm font-medium text-gray-400 mb-2">Parameters</h4>
47
- <div className="bg-po-bg border border-po-border rounded p-3 overflow-auto max-h-48">
48
- <pre className="text-xs text-gray-300 font-mono whitespace-pre-wrap">
46
+ <h4 className="text-2xs font-medium text-po-text-ghost uppercase tracking-wider mb-2">Parameters</h4>
47
+ <div className="bg-po-surface-2 border border-po-border rounded p-2.5 overflow-auto max-h-48">
48
+ <pre className="text-xs text-po-text-secondary font-mono whitespace-pre-wrap">
49
49
  {JSON.stringify(toolCall.params, null, 2)}
50
50
  </pre>
51
51
  </div>
@@ -54,9 +54,9 @@ export function ToolCallInspector({ toolCallId }: Props) {
54
54
  {/* Result */}
55
55
  {toolCall.result && (
56
56
  <div>
57
- <h4 className="text-sm font-medium text-gray-400 mb-2">Result</h4>
58
- <div className="bg-po-bg border border-po-border rounded p-3 overflow-auto max-h-64">
59
- <pre className="text-xs text-gray-300 font-mono whitespace-pre-wrap">
57
+ <h4 className="text-2xs font-medium text-po-text-ghost uppercase tracking-wider mb-2">Result</h4>
58
+ <div className="bg-po-surface-2 border border-po-border rounded p-2.5 overflow-auto max-h-64">
59
+ <pre className="text-xs text-po-text-secondary font-mono whitespace-pre-wrap">
60
60
  {toolCall.result}
61
61
  </pre>
62
62
  </div>
@@ -63,8 +63,8 @@ const STATUS_CSS_COLORS: Record<POStatus, string> = {
63
63
  }
64
64
 
65
65
  // Color for when this PO is being called by another PO (delegation)
66
- const DELEGATED_COLOR = 0x06b6d4 // cyan-500
67
- const DELEGATED_CSS_COLOR = '#06b6d4'
66
+ const DELEGATED_COLOR = 0x5a8fc2 // steel blue (matches po-status-delegated)
67
+ const DELEGATED_CSS_COLOR = '#5a8fc2'
68
68
 
69
69
  export class PONode {
70
70
  readonly id: string
@@ -35,7 +35,6 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
35
35
  }
36
36
  }, [onClose])
37
37
 
38
- // Adjust position to stay within viewport
39
38
  const adjustedStyle = {
40
39
  top: y,
41
40
  left: x,
@@ -44,7 +43,7 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
44
43
  return (
45
44
  <div
46
45
  ref={menuRef}
47
- className="fixed z-50 bg-po-surface border border-po-border rounded-lg shadow-xl py-1 min-w-[160px]"
46
+ className="fixed z-50 bg-po-surface-2 border border-po-border rounded shadow-xl py-0.5 min-w-[140px]"
48
47
  style={adjustedStyle}
49
48
  >
50
49
  {items.map((item, idx) => (
@@ -54,8 +53,10 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
54
53
  item.onClick()
55
54
  onClose()
56
55
  }}
57
- className={`w-full text-left px-3 py-1.5 text-xs hover:bg-po-bg transition-colors flex items-center gap-2 ${
58
- item.danger ? 'text-red-400 hover:text-red-300' : 'text-gray-300 hover:text-white'
56
+ className={`w-full text-left px-2.5 py-1.5 text-xs transition-colors duration-150 flex items-center gap-1.5 ${
57
+ item.danger
58
+ ? 'text-po-error hover:bg-po-surface-3'
59
+ : 'text-po-text-secondary hover:bg-po-surface-3 hover:text-po-text-primary'
59
60
  }`}
60
61
  >
61
62
  {item.icon && <span>{item.icon}</span>}
@@ -0,0 +1,69 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { useStore, useEnvData } from '../store'
3
+ import type { EnvDataEntry } from '../types'
4
+
5
+ interface EnvDataPaneProps {
6
+ sessionId: string | undefined
7
+ requestEnvData: (sessionId: string) => void
8
+ }
9
+
10
+ export function EnvDataPane({ sessionId, requestEnvData }: EnvDataPaneProps) {
11
+ const sessionRootMap = useStore((s) => s.sessionRootMap)
12
+ const rootThreadId = sessionId ? sessionRootMap[sessionId] : undefined
13
+ const entries = useEnvData(rootThreadId)
14
+ const [expandedKey, setExpandedKey] = useState<string | null>(null)
15
+
16
+ useEffect(() => {
17
+ if (sessionId) {
18
+ requestEnvData(sessionId)
19
+ }
20
+ }, [sessionId, requestEnvData])
21
+
22
+ if (entries.length === 0) {
23
+ return (
24
+ <div className="h-full flex items-center justify-center">
25
+ <span className="font-mono text-xs text-po-text-ghost">No shared data</span>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ return (
31
+ <div className="h-full overflow-auto px-2 py-1">
32
+ {entries.map((entry) => (
33
+ <EnvDataRow
34
+ key={entry.key}
35
+ entry={entry}
36
+ expanded={expandedKey === entry.key}
37
+ onToggle={() => setExpandedKey(expandedKey === entry.key ? null : entry.key)}
38
+ />
39
+ ))}
40
+ </div>
41
+ )
42
+ }
43
+
44
+ function EnvDataRow({ entry, expanded, onToggle }: { entry: EnvDataEntry; expanded: boolean; onToggle: () => void }) {
45
+ return (
46
+ <div className="border-b border-po-border last:border-b-0">
47
+ <button
48
+ onClick={onToggle}
49
+ className="w-full text-left px-1.5 py-1.5 hover:bg-po-surface-3 transition-colors duration-150 flex items-center gap-2"
50
+ >
51
+ <span className="text-2xs text-po-text-ghost">{expanded ? '▼' : '▶'}</span>
52
+ <span className="font-mono text-sm text-po-accent truncate">{entry.key}</span>
53
+ <span className="text-xs text-po-text-ghost truncate flex-1">{entry.short_description}</span>
54
+ <span className="text-xs text-po-text-ghost flex-shrink-0">{entry.stored_by}</span>
55
+ </button>
56
+ {expanded && (
57
+ <div className="px-2 pb-2">
58
+ <div className="text-xs text-po-text-ghost mb-1">
59
+ stored by <span className="text-po-text-secondary">{entry.stored_by}</span>
60
+ {entry.updated_at && <> &middot; {new Date(entry.updated_at).toLocaleTimeString()}</>}
61
+ </div>
62
+ <pre className="font-mono text-xs text-po-text-primary bg-po-surface-1 rounded p-2 overflow-auto max-h-40 whitespace-pre-wrap break-all">
63
+ {typeof entry.value === 'string' ? entry.value : JSON.stringify(entry.value, null, 2)}
64
+ </pre>
65
+ </div>
66
+ )}
67
+ </div>
68
+ )
69
+ }