@radnine/storybook-addon-claude 0.2.4 → 0.2.6

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/dist/Panel.js CHANGED
@@ -27,7 +27,7 @@ function ClaudePanel({
27
27
  // Persisted addon state for the token and port
28
28
  const [addonState, setAddonState] = (0, _managerApi.useAddonState)(_constants.ADDON_ID, {
29
29
  token: getInitialToken(),
30
- port: _constants.DEFAULT_PORT,
30
+ port: getInitialPort(),
31
31
  contextEnabled: true
32
32
  });
33
33
  const token = addonState?.token || null;
@@ -124,6 +124,25 @@ function ClaudePanel({
124
124
  });
125
125
  }
126
126
 
127
+ /**
128
+ * Read initial port from environment or global variable if available.
129
+ * Supports: window.__CLAUDE_DAEMON_PORT__ (set via managerHead in main.ts)
130
+ * process.env.STORYBOOK_CLAUDE_DAEMON_PORT (if available)
131
+ */
132
+ function getInitialPort() {
133
+ try {
134
+ // Manager-injected global (most reliable for addon panels)
135
+ if (typeof window !== 'undefined' && window.__CLAUDE_DAEMON_PORT__) {
136
+ return parseInt(window.__CLAUDE_DAEMON_PORT__, 10) || _constants.DEFAULT_PORT;
137
+ }
138
+ // Env var fallback (works in preview iframe with Vite replacement)
139
+ const envPort = typeof process !== 'undefined' && process.env?.STORYBOOK_CLAUDE_DAEMON_PORT || null;
140
+ return envPort ? parseInt(envPort, 10) : _constants.DEFAULT_PORT;
141
+ } catch {
142
+ return _constants.DEFAULT_PORT;
143
+ }
144
+ }
145
+
127
146
  /**
128
147
  * Read initial token from environment variable if available.
129
148
  */
@@ -172,6 +172,7 @@ class WebSocketClient {
172
172
  case 'output':
173
173
  case 'complete':
174
174
  case 'error':
175
+ case 'user_input':
175
176
  case 'pong':
176
177
  this._emit(msg.type, msg);
177
178
  break;
@@ -86,8 +86,14 @@ function OutputMessage({
86
86
  replay
87
87
  }) {
88
88
  if (!data) return null;
89
- const dataType = typeof data === 'object' ? data.type : null;
90
- switch (dataType) {
89
+
90
+ // Classify the message the same way the daemon's message-parser does:
91
+ // - assistant with tool_use content → tool_use
92
+ // - user with tool_result content → tool_result
93
+ // - plain user echo → hide (noise)
94
+ // - system init → hide (noise)
95
+ const classifiedType = classifyOutputType(data);
96
+ switch (classifiedType) {
91
97
  case 'assistant':
92
98
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(AssistantMessage, {
93
99
  data: data,
@@ -113,14 +119,61 @@ function OutputMessage({
113
119
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(RateLimitMessage, {
114
120
  data: data
115
121
  });
122
+ case '_skip':
123
+ return null;
116
124
  default:
117
- // If data is a classified output with message content, try to extract text
118
125
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(GenericOutputMessage, {
119
126
  data: data,
120
127
  replay: replay
121
128
  });
122
129
  }
123
130
  }
131
+
132
+ /**
133
+ * Classify a raw Claude CLI stream-json message into a display type.
134
+ *
135
+ * Claude CLI emits:
136
+ * { type: 'assistant', message: { content: [...] } } — may contain text and/or tool_use blocks
137
+ * { type: 'user', message: { content: [...] } } — echoed tool_result blocks (internal noise)
138
+ * { type: 'system', subtype: 'init', ... } — system init (internal noise)
139
+ *
140
+ * We reclassify so the UI routes to the right renderer.
141
+ */
142
+ function classifyOutputType(data) {
143
+ if (!data || typeof data !== 'object') return undefined;
144
+ const rawType = data.type;
145
+ const content = data?.message?.content || data?.content;
146
+ const contentArray = Array.isArray(content) ? content : [];
147
+ switch (rawType) {
148
+ case 'assistant':
149
+ {
150
+ const hasToolUse = contentArray.some(b => b?.type === 'tool_use');
151
+ const hasText = contentArray.some(b => b?.type === 'text' && b.text?.trim());
152
+ // Only thinking blocks and no text/tool_use — skip (internal noise)
153
+ const hasOnlyThinking = contentArray.length > 0 && contentArray.every(b => b?.type === 'thinking');
154
+ if (hasOnlyThinking) return '_skip';
155
+ // Tool use with no meaningful text → tool_use renderer
156
+ if (hasToolUse && !hasText) return 'tool_use';
157
+ return 'assistant';
158
+ }
159
+ case 'user':
160
+ {
161
+ // User messages with tool_result content are internal echoes — render as tool_result
162
+ const hasToolResult = contentArray.some(b => b?.type === 'tool_result');
163
+ if (hasToolResult) return 'tool_result';
164
+ // Plain user echo is noise — skip
165
+ return '_skip';
166
+ }
167
+ case 'system':
168
+ {
169
+ // System init messages are noise — skip
170
+ if (data.subtype === 'init') return '_skip';
171
+ return 'system';
172
+ }
173
+ default:
174
+ return rawType;
175
+ }
176
+ }
124
177
  function AssistantMessage({
125
178
  data,
126
179
  replay
@@ -71,6 +71,23 @@ function useClaudeSession(options = {}) {
71
71
  ...msg,
72
72
  id: crypto.randomUUID()
73
73
  }]);
74
+ }), client.on('user_input', msg => {
75
+ if (msg.sessionId !== sessionIdRef.current) return;
76
+ // Replayed user messages from session history — strip context prefix
77
+ let text = msg.text || '';
78
+ const ctxMarker = '[Context from Storybook]';
79
+ if (text.startsWith(ctxMarker)) {
80
+ // Strip everything up to the double-newline separator
81
+ const idx = text.indexOf('\n\n');
82
+ text = idx >= 0 ? text.slice(idx + 2) : text;
83
+ }
84
+ setMessages(prev => [...prev, {
85
+ type: 'user_input',
86
+ text,
87
+ id: crypto.randomUUID(),
88
+ timestamp: msg.timestamp,
89
+ replay: true
90
+ }]);
74
91
  }), client.on('complete', msg => {
75
92
  if (msg.sessionId !== sessionIdRef.current) return;
76
93
  setMessages(prev => [...prev, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radnine/storybook-addon-claude",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Storybook addon panel for chatting with Claude via the standalone daemon",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {