Skip to content

Commit c3b964a

Browse files
ghirparaclaude
andcommitted
Story Canvas: fit-to-screen, call arrows, character filter, role tinting, legend, route canvas button
- Fit to screen: F shortcut + Fit button (bottom-left) fits all visible blocks into view - Call vs jump arrows: hollow circle at source for call links, matching Route Canvas convention - Character filter: dropdown in filter panel dims all blocks not containing selected character's dialogue - Root/leaf role tinting: green border+bg for story start blocks, amber dashed border+bg for story end blocks - Legend: collapsible panel (bottom-left) explaining arrows, block roles, and block types - Open in Route Canvas: hover button on each block header navigates to Route Canvas tab - Fix: TDZ crash on project open caused by fitToScreen useCallback referencing visibleBlocks before its declaration - Link.type field added to carry jump/call distinction through to block-level connections Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3222e80 commit c3b964a

File tree

6 files changed

+223
-22
lines changed

6 files changed

+223
-22
lines changed

App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2743,6 +2743,7 @@ const App: React.FC = () => {
27432743
centerOnBlockRequest={centerOnBlockRequest} flashBlockRequest={flashBlockRequest}
27442744
hoverHighlightIds={hoverHighlightIds} transform={storyCanvasTransform} onTransformChange={setStoryCanvasTransform}
27452745
onCreateBlock={handleCreateBlockFromCanvas} onAddStickyNote={addStickyNote} mouseGestures={appSettings.mouseGestures}
2746+
onOpenRouteCanvas={() => handleOpenStaticTab('route-canvas')}
27462747
/>;
27472748
}
27482749
if (tab.type === 'route-canvas') {

components/CodeBlock.tsx

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface CodeBlockProps {
77
updateBlock: (id: string, newBlockData: Partial<Block>) => void;
88
deleteBlock: (id: string) => void;
99
onOpenEditor: (id: string) => void;
10+
onOpenRouteCanvas?: () => void;
1011
isSelected: boolean;
1112
isDragging: boolean;
1213
isRoot: boolean;
@@ -50,12 +51,13 @@ const BLOCK_COLORS: Record<string, { bg: string, header: string, border: string,
5051
rose: { bg: 'bg-rose-50 dark:bg-rose-900/20', header: 'bg-rose-100 dark:bg-rose-900/50', border: 'border-rose-200 dark:border-rose-800', dot: '#fda4af' },
5152
};
5253

53-
const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(({
54-
block,
55-
analysisResult,
56-
updateBlock,
57-
deleteBlock,
54+
const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(({
55+
block,
56+
analysisResult,
57+
updateBlock,
58+
deleteBlock,
5859
onOpenEditor,
60+
onOpenRouteCanvas,
5961
isSelected,
6062
isDragging,
6163
isRoot,
@@ -167,6 +169,10 @@ const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(({
167169
borderClass = 'border-red-500/50 dark:border-red-400/50';
168170
} else if (isScreenBlock) {
169171
borderClass = 'border-teal-500/50 dark:border-teal-400/50';
172+
} else if (isRoot) {
173+
borderClass = 'border-green-400 dark:border-green-500';
174+
} else if (isLeaf) {
175+
borderClass = 'border-dashed border-amber-400 dark:border-amber-500';
170176
} else {
171177
borderClass = defaultColor.border;
172178
}
@@ -190,7 +196,13 @@ const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(({
190196
headerClass = defaultColor.header;
191197
}
192198

193-
const bgClass = customColor ? customColor.bg : defaultColor.bg;
199+
const bgClass = customColor
200+
? customColor.bg
201+
: isRoot && !isConfigBlock && !isScreenBlock
202+
? 'bg-green-50 dark:bg-green-900/10'
203+
: isLeaf && !isConfigBlock && !isScreenBlock
204+
? 'bg-amber-50 dark:bg-amber-900/10'
205+
: defaultColor.bg;
194206

195207
const handleColorSelect = (colorKey: string) => {
196208
updateBlock(block.id, { color: colorKey === 'default' ? undefined : colorKey });
@@ -201,7 +213,7 @@ const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(({
201213
<div
202214
ref={ref}
203215
data-block-id={block.id}
204-
className={`code-block-wrapper absolute ${bgClass} rounded-lg shadow-2xl border-2 ${borderClass} ${shadowClass} flex flex-col transition-colors duration-200 ${isDimmed ? 'opacity-30' : ''} ${isFlashing ? 'flash-block' : isHoverHighlighted ? 'pulse-block heatmap-highlight' : ''}`}
216+
className={`code-block-wrapper group absolute ${bgClass} rounded-lg shadow-2xl border-2 ${borderClass} ${shadowClass} flex flex-col transition-colors duration-200 ${isDimmed ? 'opacity-30' : ''} ${isFlashing ? 'flash-block' : isHoverHighlighted ? 'pulse-block heatmap-highlight' : ''}`}
205217
style={{
206218
left: block.position.x,
207219
top: block.position.y,
@@ -261,6 +273,18 @@ const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(({
261273
)}
262274
</div>
263275
<button onClick={() => onOpenEditor(block.id)} className="p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white" title="Open in Tab"><svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-1 1h-2a1 1 0 01-1-1v-3.586L3.293 6.707A1 1 0 013 6V3zm3.146 2.146a.5.5 0 01.708 0l2.5 2.5a.5.5 0 010 .708l-2.5 2.5a.5.5 0 01-.708-.708L7.793 8 6.146 6.354a.5.5 0 010-.708z" clipRule="evenodd" /></svg></button>
276+
{onOpenRouteCanvas && (
277+
<button
278+
onClick={(e) => { e.stopPropagation(); onOpenRouteCanvas(); }}
279+
onPointerDown={e => e.stopPropagation()}
280+
className="p-1 text-gray-500 dark:text-gray-400 hover:text-indigo-600 dark:hover:text-indigo-400 opacity-0 group-hover:opacity-100 transition-opacity"
281+
title="Open in Route Canvas"
282+
>
283+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
284+
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
285+
</svg>
286+
</button>
287+
)}
264288
<button onClick={() => deleteBlock(block.id)} className="p-1 text-gray-500 dark:text-gray-400 hover:text-black dark:hover:text-white" title="Delete Block"><svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /></svg></button>
265289
</div>
266290
</div>

0 commit comments

Comments
 (0)