@djangocfg/ui-tools 2.1.294 → 2.1.298
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/{DocsLayout-XB55R7YG.mjs → DocsLayout-74WIW7L3.mjs} +3 -3
- package/dist/{DocsLayout-XB55R7YG.mjs.map → DocsLayout-74WIW7L3.mjs.map} +1 -1
- package/dist/{DocsLayout-PLWQJBGU.cjs → DocsLayout-IA55EXRN.cjs} +48 -48
- package/dist/{DocsLayout-PLWQJBGU.cjs.map → DocsLayout-IA55EXRN.cjs.map} +1 -1
- package/dist/{chunk-EFWOJPA6.mjs → chunk-2BBXP3DH.mjs} +42 -6
- package/dist/chunk-2BBXP3DH.mjs.map +1 -0
- package/dist/{chunk-5Q4UMSWB.cjs → chunk-Q6FNLXLZ.cjs} +42 -6
- package/dist/chunk-Q6FNLXLZ.cjs.map +1 -0
- package/dist/index.cjs +9 -9
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.mjs +4 -4
- package/package.json +6 -6
- package/src/components/markdown/MarkdownMessage.tsx +109 -5
- package/dist/chunk-5Q4UMSWB.cjs.map +0 -1
- package/dist/chunk-EFWOJPA6.mjs.map +0 -1
- package/src/tools/ImageViewer/@refactoring/00-PLAN.md +0 -71
- package/src/tools/ImageViewer/@refactoring/01-TYPES.md +0 -121
- package/src/tools/ImageViewer/@refactoring/02-UTILS.md +0 -143
- package/src/tools/ImageViewer/@refactoring/03-HOOKS.md +0 -261
- package/src/tools/ImageViewer/@refactoring/04-COMPONENTS.md +0 -427
- package/src/tools/ImageViewer/@refactoring/05-EXECUTION-CHECKLIST.md +0 -126
- package/src/tools/OpenapiViewer/.claude/.sidecar/activity.jsonl +0 -6
- package/src/tools/OpenapiViewer/.claude/.sidecar/history/2026-04-22.md +0 -35
- package/src/tools/OpenapiViewer/.claude/.sidecar/map_cache.json +0 -30
- package/src/tools/OpenapiViewer/.claude/.sidecar/review.md +0 -35
- package/src/tools/OpenapiViewer/.claude/.sidecar/scan.log +0 -3
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-001.md +0 -18
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-002.md +0 -18
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-003.md +0 -18
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-004.md +0 -18
- package/src/tools/OpenapiViewer/.claude/.sidecar/tasks/T-005.md +0 -18
- package/src/tools/OpenapiViewer/.claude/.sidecar/usage.json +0 -5
- package/src/tools/OpenapiViewer/.claude/project-map.md +0 -23
- package/src/tools/VideoPlayer/@refactoring/00-PLAN.md +0 -91
- package/src/tools/VideoPlayer/@refactoring/01-TYPES.md +0 -284
- package/src/tools/VideoPlayer/@refactoring/02-UTILS.md +0 -141
- package/src/tools/VideoPlayer/@refactoring/03-HOOKS.md +0 -178
- package/src/tools/VideoPlayer/@refactoring/04-COMPONENTS.md +0 -95
- package/src/tools/VideoPlayer/@refactoring/05-EXECUTION-CHECKLIST.md +0 -139
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
# Phase 4: Components Extraction
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
|
|
5
|
-
Split the monolithic ImageViewer.tsx into separate component files.
|
|
6
|
-
|
|
7
|
-
## Files to Create
|
|
8
|
-
|
|
9
|
-
### components/ImageToolbar.tsx
|
|
10
|
-
|
|
11
|
-
Floating toolbar with zoom/rotate/flip controls.
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
'use client';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* ImageToolbar - Floating toolbar for image controls
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { useCallback } from 'react';
|
|
21
|
-
import { Button, cn } from '@djangocfg/ui-core';
|
|
22
|
-
import {
|
|
23
|
-
ZoomIn,
|
|
24
|
-
ZoomOut,
|
|
25
|
-
RotateCw,
|
|
26
|
-
FlipHorizontal,
|
|
27
|
-
FlipVertical,
|
|
28
|
-
Maximize2,
|
|
29
|
-
Expand,
|
|
30
|
-
} from 'lucide-react';
|
|
31
|
-
import {
|
|
32
|
-
DropdownMenu,
|
|
33
|
-
DropdownMenuContent,
|
|
34
|
-
DropdownMenuItem,
|
|
35
|
-
DropdownMenuTrigger,
|
|
36
|
-
} from '../../components/dropdown-menu';
|
|
37
|
-
import { useControls } from 'react-zoom-pan-pinch';
|
|
38
|
-
import { ZOOM_PRESETS, MIN_ZOOM, MAX_ZOOM } from '../utils';
|
|
39
|
-
import type { ImageToolbarProps } from '../types';
|
|
40
|
-
|
|
41
|
-
export function ImageToolbar({
|
|
42
|
-
scale,
|
|
43
|
-
onExpand,
|
|
44
|
-
onRotate,
|
|
45
|
-
onFlipH,
|
|
46
|
-
onFlipV,
|
|
47
|
-
flipH,
|
|
48
|
-
flipV,
|
|
49
|
-
inDialog = false,
|
|
50
|
-
}: ImageToolbarProps) {
|
|
51
|
-
const { zoomIn, zoomOut, resetTransform, centerView, setTransform } = useControls();
|
|
52
|
-
|
|
53
|
-
// Calculate zoom label
|
|
54
|
-
const zoomLabel = `${Math.round(scale * 100)}%`;
|
|
55
|
-
|
|
56
|
-
// Handle zoom preset selection
|
|
57
|
-
const handleZoomPreset = useCallback(
|
|
58
|
-
(value: number) => {
|
|
59
|
-
if (value === -1) {
|
|
60
|
-
// Fit to view
|
|
61
|
-
resetTransform();
|
|
62
|
-
} else {
|
|
63
|
-
setTransform(0, 0, value);
|
|
64
|
-
centerView(value);
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
[resetTransform, setTransform, centerView]
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1 bg-background/95 backdrop-blur-sm border rounded-lg p-1.5 shadow-lg">
|
|
72
|
-
{/* Zoom Out */}
|
|
73
|
-
<Button
|
|
74
|
-
variant="ghost"
|
|
75
|
-
size="icon"
|
|
76
|
-
className="h-8 w-8"
|
|
77
|
-
onClick={() => zoomOut()}
|
|
78
|
-
disabled={scale <= MIN_ZOOM}
|
|
79
|
-
title="Zoom out"
|
|
80
|
-
>
|
|
81
|
-
<ZoomOut className="h-4 w-4" />
|
|
82
|
-
</Button>
|
|
83
|
-
|
|
84
|
-
{/* Zoom Dropdown */}
|
|
85
|
-
<DropdownMenu>
|
|
86
|
-
<DropdownMenuTrigger asChild>
|
|
87
|
-
<Button variant="ghost" size="sm" className="h-8 min-w-16 px-2 font-mono text-xs">
|
|
88
|
-
{zoomLabel}
|
|
89
|
-
</Button>
|
|
90
|
-
</DropdownMenuTrigger>
|
|
91
|
-
<DropdownMenuContent align="center">
|
|
92
|
-
{ZOOM_PRESETS.map((preset) => (
|
|
93
|
-
<DropdownMenuItem
|
|
94
|
-
key={preset.value}
|
|
95
|
-
onClick={() => handleZoomPreset(preset.value)}
|
|
96
|
-
>
|
|
97
|
-
{preset.label}
|
|
98
|
-
</DropdownMenuItem>
|
|
99
|
-
))}
|
|
100
|
-
</DropdownMenuContent>
|
|
101
|
-
</DropdownMenu>
|
|
102
|
-
|
|
103
|
-
{/* Zoom In */}
|
|
104
|
-
<Button
|
|
105
|
-
variant="ghost"
|
|
106
|
-
size="icon"
|
|
107
|
-
className="h-8 w-8"
|
|
108
|
-
onClick={() => zoomIn()}
|
|
109
|
-
disabled={scale >= MAX_ZOOM}
|
|
110
|
-
title="Zoom in"
|
|
111
|
-
>
|
|
112
|
-
<ZoomIn className="h-4 w-4" />
|
|
113
|
-
</Button>
|
|
114
|
-
|
|
115
|
-
<div className="w-px h-6 bg-border mx-1" />
|
|
116
|
-
|
|
117
|
-
{/* Fit to View */}
|
|
118
|
-
<Button
|
|
119
|
-
variant="ghost"
|
|
120
|
-
size="icon"
|
|
121
|
-
className="h-8 w-8"
|
|
122
|
-
onClick={() => resetTransform()}
|
|
123
|
-
title="Fit to view"
|
|
124
|
-
>
|
|
125
|
-
<Maximize2 className="h-4 w-4" />
|
|
126
|
-
</Button>
|
|
127
|
-
|
|
128
|
-
{/* Rotate */}
|
|
129
|
-
<Button
|
|
130
|
-
variant="ghost"
|
|
131
|
-
size="icon"
|
|
132
|
-
className="h-8 w-8"
|
|
133
|
-
onClick={onRotate}
|
|
134
|
-
title="Rotate 90°"
|
|
135
|
-
>
|
|
136
|
-
<RotateCw className="h-4 w-4" />
|
|
137
|
-
</Button>
|
|
138
|
-
|
|
139
|
-
{/* Flip Horizontal */}
|
|
140
|
-
<Button
|
|
141
|
-
variant="ghost"
|
|
142
|
-
size="icon"
|
|
143
|
-
className={cn('h-8 w-8', flipH && 'bg-muted text-primary')}
|
|
144
|
-
onClick={onFlipH}
|
|
145
|
-
title="Flip horizontal"
|
|
146
|
-
>
|
|
147
|
-
<FlipHorizontal className="h-4 w-4" />
|
|
148
|
-
</Button>
|
|
149
|
-
|
|
150
|
-
{/* Flip Vertical */}
|
|
151
|
-
<Button
|
|
152
|
-
variant="ghost"
|
|
153
|
-
size="icon"
|
|
154
|
-
className={cn('h-8 w-8', flipV && 'bg-muted text-primary')}
|
|
155
|
-
onClick={onFlipV}
|
|
156
|
-
title="Flip vertical"
|
|
157
|
-
>
|
|
158
|
-
<FlipVertical className="h-4 w-4" />
|
|
159
|
-
</Button>
|
|
160
|
-
|
|
161
|
-
{/* Expand (hidden in dialog) */}
|
|
162
|
-
{!inDialog && (
|
|
163
|
-
<>
|
|
164
|
-
<div className="w-px h-6 bg-border mx-1" />
|
|
165
|
-
<Button
|
|
166
|
-
variant="ghost"
|
|
167
|
-
size="icon"
|
|
168
|
-
className="h-8 w-8"
|
|
169
|
-
onClick={onExpand}
|
|
170
|
-
title="Expand fullscreen"
|
|
171
|
-
>
|
|
172
|
-
<Expand className="h-4 w-4" />
|
|
173
|
-
</Button>
|
|
174
|
-
</>
|
|
175
|
-
)}
|
|
176
|
-
</div>
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### components/ImageInfo.tsx
|
|
182
|
-
|
|
183
|
-
Displays image dimensions badge.
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
'use client';
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* ImageInfo - Displays image dimensions
|
|
190
|
-
*/
|
|
191
|
-
|
|
192
|
-
import { useEffect, useState } from 'react';
|
|
193
|
-
import { useImageCache } from '../../../stores/mediaCache';
|
|
194
|
-
import type { ImageInfoProps } from '../types';
|
|
195
|
-
|
|
196
|
-
export function ImageInfo({ src, contentKey }: ImageInfoProps) {
|
|
197
|
-
const { getDimensions, cacheDimensions } = useImageCache();
|
|
198
|
-
|
|
199
|
-
const [dimensions, setDimensions] = useState<{ width: number; height: number } | null>(
|
|
200
|
-
() => getDimensions(contentKey)
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
useEffect(() => {
|
|
204
|
-
// Already have dimensions
|
|
205
|
-
if (dimensions) return;
|
|
206
|
-
|
|
207
|
-
// Load image to get dimensions
|
|
208
|
-
const img = new Image();
|
|
209
|
-
img.onload = () => {
|
|
210
|
-
const dims = { width: img.naturalWidth, height: img.naturalHeight };
|
|
211
|
-
cacheDimensions(contentKey, dims);
|
|
212
|
-
setDimensions(dims);
|
|
213
|
-
};
|
|
214
|
-
img.src = src;
|
|
215
|
-
}, [src, contentKey, dimensions, cacheDimensions]);
|
|
216
|
-
|
|
217
|
-
if (!dimensions) return null;
|
|
218
|
-
|
|
219
|
-
return (
|
|
220
|
-
<div className="absolute top-2 right-2 z-10 px-2 py-1 text-xs font-mono bg-background/80 backdrop-blur-sm border rounded text-muted-foreground">
|
|
221
|
-
{dimensions.width} × {dimensions.height}
|
|
222
|
-
</div>
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### components/ImageViewer.tsx
|
|
228
|
-
|
|
229
|
-
Main component, now simplified.
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
'use client';
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* ImageViewer - Image display with zoom/pan/rotate capabilities
|
|
236
|
-
*/
|
|
237
|
-
|
|
238
|
-
import { useRef, useEffect, useCallback, useState } from 'react';
|
|
239
|
-
import { TransformWrapper, TransformComponent, useControls } from 'react-zoom-pan-pinch';
|
|
240
|
-
import { Dialog, DialogContent, Alert, AlertDescription, cn } from '@djangocfg/ui-core';
|
|
241
|
-
import { AlertCircle, ImageIcon } from 'lucide-react';
|
|
242
|
-
|
|
243
|
-
import { ImageToolbar } from './ImageToolbar';
|
|
244
|
-
import { ImageInfo } from './ImageInfo';
|
|
245
|
-
import { useImageTransform, useImageLoading } from '../hooks';
|
|
246
|
-
import { MIN_ZOOM, MAX_ZOOM } from '../utils';
|
|
247
|
-
import type { ImageViewerProps } from '../types';
|
|
248
|
-
|
|
249
|
-
// Controls wrapper for accessing zoom state
|
|
250
|
-
function ImageViewerContent({
|
|
251
|
-
file,
|
|
252
|
-
src,
|
|
253
|
-
lqip,
|
|
254
|
-
isLoading,
|
|
255
|
-
isReady,
|
|
256
|
-
contentKey,
|
|
257
|
-
isLarge,
|
|
258
|
-
transform,
|
|
259
|
-
transformStyle,
|
|
260
|
-
rotate,
|
|
261
|
-
flipH,
|
|
262
|
-
flipV,
|
|
263
|
-
onExpand,
|
|
264
|
-
inDialog,
|
|
265
|
-
}: /* props type */) {
|
|
266
|
-
const { zoomIn, zoomOut, resetTransform } = useControls();
|
|
267
|
-
const [scale, setScale] = useState(1);
|
|
268
|
-
|
|
269
|
-
// Keyboard shortcuts
|
|
270
|
-
useEffect(() => {
|
|
271
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
272
|
-
if (e.key === '+' || e.key === '=') zoomIn();
|
|
273
|
-
if (e.key === '-') zoomOut();
|
|
274
|
-
if (e.key === '0') resetTransform();
|
|
275
|
-
if (e.key === 'r' || e.key === 'R') rotate();
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
279
|
-
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
280
|
-
}, [zoomIn, zoomOut, resetTransform, rotate]);
|
|
281
|
-
|
|
282
|
-
return (
|
|
283
|
-
<>
|
|
284
|
-
<ImageToolbar
|
|
285
|
-
scale={scale}
|
|
286
|
-
onExpand={onExpand}
|
|
287
|
-
onRotate={rotate}
|
|
288
|
-
onFlipH={flipH}
|
|
289
|
-
onFlipV={flipV}
|
|
290
|
-
flipH={transform.flipH}
|
|
291
|
-
flipV={transform.flipV}
|
|
292
|
-
inDialog={inDialog}
|
|
293
|
-
/>
|
|
294
|
-
|
|
295
|
-
<ImageInfo src={src} contentKey={contentKey} />
|
|
296
|
-
|
|
297
|
-
<TransformComponent wrapperClass="!w-full !h-full" contentClass="!w-full !h-full">
|
|
298
|
-
<div className="relative w-full h-full flex items-center justify-center">
|
|
299
|
-
{/* LQIP placeholder */}
|
|
300
|
-
{isLarge && lqip && !isReady && (
|
|
301
|
-
<img
|
|
302
|
-
src={lqip}
|
|
303
|
-
alt=""
|
|
304
|
-
aria-hidden="true"
|
|
305
|
-
className="absolute inset-0 w-full h-full object-contain blur-lg scale-105"
|
|
306
|
-
/>
|
|
307
|
-
)}
|
|
308
|
-
|
|
309
|
-
{/* Main image */}
|
|
310
|
-
<img
|
|
311
|
-
src={src}
|
|
312
|
-
alt={file.name}
|
|
313
|
-
className={cn(
|
|
314
|
-
'max-w-full max-h-full object-contain transition-opacity',
|
|
315
|
-
isLoading && 'opacity-50'
|
|
316
|
-
)}
|
|
317
|
-
style={{ transform: transformStyle }}
|
|
318
|
-
draggable={false}
|
|
319
|
-
/>
|
|
320
|
-
</div>
|
|
321
|
-
</TransformComponent>
|
|
322
|
-
</>
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export function ImageViewer({ file, content, inDialog = false }: ImageViewerProps) {
|
|
327
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
328
|
-
|
|
329
|
-
// Loading state
|
|
330
|
-
const {
|
|
331
|
-
src,
|
|
332
|
-
lqip,
|
|
333
|
-
isLoading,
|
|
334
|
-
isReady,
|
|
335
|
-
error,
|
|
336
|
-
contentKey,
|
|
337
|
-
isOversized,
|
|
338
|
-
isLarge,
|
|
339
|
-
} = useImageLoading({ content, mimeType: file.mimeType });
|
|
340
|
-
|
|
341
|
-
// Transform state
|
|
342
|
-
const { transform, rotate, flipH, flipV, transformStyle } = useImageTransform({
|
|
343
|
-
resetKey: file.path,
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
// Error state
|
|
347
|
-
if (error || isOversized) {
|
|
348
|
-
return (
|
|
349
|
-
<Alert variant="destructive">
|
|
350
|
-
<AlertCircle className="h-4 w-4" />
|
|
351
|
-
<AlertDescription>{error || 'Image too large to display'}</AlertDescription>
|
|
352
|
-
</Alert>
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Loading state (no src yet)
|
|
357
|
-
if (!src) {
|
|
358
|
-
return (
|
|
359
|
-
<div className="flex items-center justify-center h-64 bg-muted/30">
|
|
360
|
-
<ImageIcon className="h-8 w-8 text-muted-foreground animate-pulse" />
|
|
361
|
-
</div>
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const viewer = (
|
|
366
|
-
<TransformWrapper
|
|
367
|
-
initialScale={1}
|
|
368
|
-
minScale={MIN_ZOOM}
|
|
369
|
-
maxScale={MAX_ZOOM}
|
|
370
|
-
centerOnInit
|
|
371
|
-
limitToBounds={false}
|
|
372
|
-
onTransformed={(_, state) => {/* update scale */}}
|
|
373
|
-
>
|
|
374
|
-
<ImageViewerContent
|
|
375
|
-
file={file}
|
|
376
|
-
src={src}
|
|
377
|
-
lqip={lqip}
|
|
378
|
-
isLoading={isLoading}
|
|
379
|
-
isReady={isReady}
|
|
380
|
-
contentKey={contentKey}
|
|
381
|
-
isLarge={isLarge}
|
|
382
|
-
transform={transform}
|
|
383
|
-
transformStyle={transformStyle}
|
|
384
|
-
rotate={rotate}
|
|
385
|
-
flipH={flipH}
|
|
386
|
-
flipV={flipV}
|
|
387
|
-
onExpand={() => setDialogOpen(true)}
|
|
388
|
-
inDialog={inDialog}
|
|
389
|
-
/>
|
|
390
|
-
</TransformWrapper>
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
return (
|
|
394
|
-
<>
|
|
395
|
-
<div className="relative w-full h-[400px] overflow-hidden bg-checkerboard rounded-lg">
|
|
396
|
-
{viewer}
|
|
397
|
-
</div>
|
|
398
|
-
|
|
399
|
-
{/* Fullscreen dialog */}
|
|
400
|
-
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
|
401
|
-
<DialogContent className="max-w-[95vw] max-h-[95vh] p-0">
|
|
402
|
-
<ImageViewer file={file} content={content} inDialog />
|
|
403
|
-
</DialogContent>
|
|
404
|
-
</Dialog>
|
|
405
|
-
</>
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### components/index.ts
|
|
411
|
-
|
|
412
|
-
```typescript
|
|
413
|
-
/**
|
|
414
|
-
* ImageViewer components - Public API
|
|
415
|
-
*/
|
|
416
|
-
|
|
417
|
-
export { ImageViewer } from './ImageViewer';
|
|
418
|
-
export { ImageToolbar } from './ImageToolbar';
|
|
419
|
-
export { ImageInfo } from './ImageInfo';
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
## Notes
|
|
423
|
-
|
|
424
|
-
- ImageToolbar uses `useControls` from react-zoom-pan-pinch
|
|
425
|
-
- ImageInfo caches dimensions for performance
|
|
426
|
-
- Main component orchestrates everything
|
|
427
|
-
- Fullscreen dialog renders nested ImageViewer with `inDialog` flag
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
# Execution Checklist
|
|
2
|
-
|
|
3
|
-
## Pre-flight
|
|
4
|
-
|
|
5
|
-
- [ ] Read current ImageViewer.tsx
|
|
6
|
-
- [ ] Backup or rename original file
|
|
7
|
-
- [ ] Create folder structure
|
|
8
|
-
|
|
9
|
-
## Phase 1: Folder Structure
|
|
10
|
-
|
|
11
|
-
- [ ] Create `components/` directory
|
|
12
|
-
- [ ] Create `hooks/` directory
|
|
13
|
-
- [ ] Create `utils/` directory
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
mkdir -p components hooks utils
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Phase 2: Types
|
|
20
|
-
|
|
21
|
-
- [ ] Create `types.ts` with all interfaces
|
|
22
|
-
- ImageFile
|
|
23
|
-
- ImageViewerProps
|
|
24
|
-
- ImageTransform
|
|
25
|
-
- ZoomPreset
|
|
26
|
-
- ImageToolbarProps
|
|
27
|
-
- ImageInfoProps
|
|
28
|
-
- [ ] Run `pnpm check`
|
|
29
|
-
|
|
30
|
-
## Phase 3: Utils
|
|
31
|
-
|
|
32
|
-
- [ ] Create `utils/constants.ts`
|
|
33
|
-
- MAX_IMAGE_SIZE, WARN_IMAGE_SIZE
|
|
34
|
-
- MIN_ZOOM, MAX_ZOOM
|
|
35
|
-
- ZOOM_PRESETS
|
|
36
|
-
- DEFAULT_TRANSFORM
|
|
37
|
-
- [ ] Create `utils/lqip.ts`
|
|
38
|
-
- createLQIP function
|
|
39
|
-
- [ ] Create `utils/index.ts`
|
|
40
|
-
- Re-export all
|
|
41
|
-
- [ ] Run `pnpm check`
|
|
42
|
-
|
|
43
|
-
## Phase 4: Hooks
|
|
44
|
-
|
|
45
|
-
- [ ] Create `hooks/useImageTransform.ts`
|
|
46
|
-
- Transform state management
|
|
47
|
-
- rotate, flipH, flipV callbacks
|
|
48
|
-
- transformStyle computed value
|
|
49
|
-
- [ ] Create `hooks/useImageLoading.ts`
|
|
50
|
-
- Blob URL management
|
|
51
|
-
- LQIP generation
|
|
52
|
-
- Loading/error states
|
|
53
|
-
- [ ] Create `hooks/index.ts`
|
|
54
|
-
- Re-export all
|
|
55
|
-
- [ ] Run `pnpm check`
|
|
56
|
-
|
|
57
|
-
## Phase 5: Components
|
|
58
|
-
|
|
59
|
-
- [ ] Create `components/ImageToolbar.tsx`
|
|
60
|
-
- Zoom controls
|
|
61
|
-
- Rotate/flip buttons
|
|
62
|
-
- Preset dropdown
|
|
63
|
-
- [ ] Create `components/ImageInfo.tsx`
|
|
64
|
-
- Dimensions display
|
|
65
|
-
- [ ] Create `components/ImageViewer.tsx`
|
|
66
|
-
- Main component (simplified)
|
|
67
|
-
- Uses hooks
|
|
68
|
-
- Includes fullscreen dialog
|
|
69
|
-
- [ ] Create `components/index.ts`
|
|
70
|
-
- Re-export all
|
|
71
|
-
- [ ] Run `pnpm check`
|
|
72
|
-
|
|
73
|
-
## Phase 6: Main Index
|
|
74
|
-
|
|
75
|
-
- [ ] Update `index.ts` with new imports
|
|
76
|
-
- Export ImageViewer from components
|
|
77
|
-
- Export types
|
|
78
|
-
- Export hooks (if public)
|
|
79
|
-
- [ ] Run `pnpm check`
|
|
80
|
-
|
|
81
|
-
## Phase 7: Cleanup
|
|
82
|
-
|
|
83
|
-
- [ ] Delete old `ImageViewer.tsx`
|
|
84
|
-
- [ ] Verify all exports work
|
|
85
|
-
- [ ] Run `pnpm check`
|
|
86
|
-
- [ ] Run `pnpm build` (optional)
|
|
87
|
-
|
|
88
|
-
## Post-flight
|
|
89
|
-
|
|
90
|
-
- [ ] Test in browser
|
|
91
|
-
- [ ] Verify zoom/pan/rotate work
|
|
92
|
-
- [ ] Verify fullscreen works
|
|
93
|
-
- [ ] Verify LQIP loads for large images
|
|
94
|
-
- [ ] Verify error states display
|
|
95
|
-
|
|
96
|
-
## Final Structure
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
ImageViewer/
|
|
100
|
-
├── index.ts
|
|
101
|
-
├── types.ts
|
|
102
|
-
├── README.md
|
|
103
|
-
├── components/
|
|
104
|
-
│ ├── index.ts
|
|
105
|
-
│ ├── ImageViewer.tsx
|
|
106
|
-
│ ├── ImageToolbar.tsx
|
|
107
|
-
│ └── ImageInfo.tsx
|
|
108
|
-
├── hooks/
|
|
109
|
-
│ ├── index.ts
|
|
110
|
-
│ ├── useImageTransform.ts
|
|
111
|
-
│ └── useImageLoading.ts
|
|
112
|
-
└── utils/
|
|
113
|
-
├── index.ts
|
|
114
|
-
├── constants.ts
|
|
115
|
-
└── lqip.ts
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Metrics
|
|
119
|
-
|
|
120
|
-
| Metric | Before | After |
|
|
121
|
-
|--------|--------|-------|
|
|
122
|
-
| Main file lines | 589 | ~150 |
|
|
123
|
-
| Total files | 3 | 12 |
|
|
124
|
-
| Sub-components | 2 inline | 2 separate |
|
|
125
|
-
| Custom hooks | 0 | 2 |
|
|
126
|
-
| Utility files | 0 | 2 |
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
{"ts":"2026-04-22T13:11:47.381936Z","action":"map","tokens":2297,"model":"deepseek/deepseek-v3.2-20251201","details":{"directories":7,"entry_points":7}}
|
|
2
|
-
{"ts":"2026-04-22T13:12:54.534624Z","action":"map","tokens":0,"model":"cache","details":{"directories":7,"entry_points":5}}
|
|
3
|
-
{"ts":"2026-04-22T13:14:16.326555Z","action":"map","tokens":0,"model":"cache","details":{"directories":7,"entry_points":5}}
|
|
4
|
-
{"ts":"2026-04-22T13:15:40.112071Z","action":"map","tokens":0,"model":"cache","details":{"directories":7,"entry_points":5}}
|
|
5
|
-
{"ts":"2026-04-22T16:47:13.863898Z","action":"update_check","tokens":0,"model":"deepseek/deepseek-v3.2","details":{}}
|
|
6
|
-
{"ts":"2026-04-22T16:47:48.099123Z","action":"review","tokens":1941,"model":"deepseek/deepseek-v3.2","details":{"items_found":5}}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# Sidecar Review -- 2026-04-22T16:47:48.098166+00:00
|
|
2
|
-
|
|
3
|
-
## Staleness
|
|
4
|
-
|
|
5
|
-
- [?] The project map was generated on 2026-04-22, but it describes a React library architecture that contradicts the Django/Next.js work shown in commits from 2026-04-17 to 2026-04-22. The map's content is stale relative to the project's true evolution.
|
|
6
|
-
Files: .claude/project-map.md
|
|
7
|
-
Action: Regenerate the project map using a tool that accurately reflects the current codebase structure and technology stack.
|
|
8
|
-
(id: 4bd49eecdaa4)
|
|
9
|
-
|
|
10
|
-
## Contradictions
|
|
11
|
-
|
|
12
|
-
- [!] The project map describes a React component library for an OpenAPI viewer, but recent git commits are for a Django/Next.js project (e.g., 'feat(drf-spectacular)', 'refactor(layouts): migrate from SmartNavLink to LinkComponentProvider'). The documentation's description does not match the project's actual activity.
|
|
13
|
-
Files: .claude/project-map.md
|
|
14
|
-
Action: Investigate the project's true nature. Update the project map description to reflect the actual tech stack (Django, Next.js, DRF Spectacular) or confirm the correct documentation is being analyzed.
|
|
15
|
-
(id: 63e451e12988)
|
|
16
|
-
|
|
17
|
-
- [?] The project map's 'Entry Points' list React component entry files (e.g., './index.tsx'), but commits suggest a Django/Next.js application with different entry points (e.g., Django's manage.py, Next.js's pages/).
|
|
18
|
-
Files: .claude/project-map.md
|
|
19
|
-
Action: Update the entry points list in the project map to reflect the actual application entry points (e.g., main Django ASGI/WSGI file, Next.js entry).
|
|
20
|
-
(id: ba6f809d9f15)
|
|
21
|
-
|
|
22
|
-
## Missing Documentation
|
|
23
|
-
|
|
24
|
-
- [!] The project map lists source directories (components, context, hooks, utils) typical of a React library, but recent commits mention backend (Django) and frontend framework (Next.js) concepts not covered. Key areas like 'layouts/', 'navigation/', and Django app structure have no documentation in the provided map.
|
|
25
|
-
Files: .claude/project-map.md
|
|
26
|
-
Action: Expand the project map to document the actual codebase structure, including Django apps, Next.js pages/layouts, and how the frontend and backend integrate.
|
|
27
|
-
(id: 4654646dd66d)
|
|
28
|
-
|
|
29
|
-
- [!] No dependencies are listed in the analysis, but recent commits reference specific packages and tools (e.g., 'drf-spectacular', 'Nextra'). The project map provides no dependency information, which is a critical gap for understanding the project's environment.
|
|
30
|
-
Files: .claude/project-map.md
|
|
31
|
-
Action: Add a 'Dependencies' section to the project map or ensure dependency files (pyproject.toml, package.json) are included in the documentation analysis.
|
|
32
|
-
(id: c81e59d10f1a)
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
Model: deepseek/deepseek-v3.2 | Tokens: 1941
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
".": {
|
|
3
|
-
"hash": "1f34f83a5cbb5246",
|
|
4
|
-
"annotation": "Primary component exports and entry points for the OpenAPI viewer library."
|
|
5
|
-
},
|
|
6
|
-
"components": {
|
|
7
|
-
"hash": "dcdc3e0b3362edb8",
|
|
8
|
-
"annotation": "Re-export barrel file for the library's public component APIs."
|
|
9
|
-
},
|
|
10
|
-
"components/DocsLayout": {
|
|
11
|
-
"hash": "c22ac1e1ddff2ec6",
|
|
12
|
-
"annotation": "Main documentation layout component and its sub-components, handling sidebar, API display, and interactive playground sheet."
|
|
13
|
-
},
|
|
14
|
-
"components/shared": {
|
|
15
|
-
"hash": "9dc41c51197bb209",
|
|
16
|
-
"annotation": "Shared UI panels (request/response editors) used by the DocsLayout and mobile interactions."
|
|
17
|
-
},
|
|
18
|
-
"context": {
|
|
19
|
-
"hash": "1d8732f878128b05",
|
|
20
|
-
"annotation": "React context for managing the playground state, such as endpoint drafts and API calls."
|
|
21
|
-
},
|
|
22
|
-
"hooks": {
|
|
23
|
-
"hash": "b1e8093c6694ba36",
|
|
24
|
-
"annotation": "Custom React hooks for managing state like API schemas, endpoint drafts, and responsive behavior."
|
|
25
|
-
},
|
|
26
|
-
"utils": {
|
|
27
|
-
"hash": "e88f8c7b59cf3776",
|
|
28
|
-
"annotation": "Utility functions for API key management, URL building, schema exporting, and data formatting."
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# Sidecar Review -- 2026-04-22T16:47:48.098166+00:00
|
|
2
|
-
|
|
3
|
-
## Staleness
|
|
4
|
-
|
|
5
|
-
- [?] The project map was generated on 2026-04-22, but it describes a React library architecture that contradicts the Django/Next.js work shown in commits from 2026-04-17 to 2026-04-22. The map's content is stale relative to the project's true evolution.
|
|
6
|
-
Files: .claude/project-map.md
|
|
7
|
-
Action: Regenerate the project map using a tool that accurately reflects the current codebase structure and technology stack.
|
|
8
|
-
(id: 4bd49eecdaa4)
|
|
9
|
-
|
|
10
|
-
## Contradictions
|
|
11
|
-
|
|
12
|
-
- [!] The project map describes a React component library for an OpenAPI viewer, but recent git commits are for a Django/Next.js project (e.g., 'feat(drf-spectacular)', 'refactor(layouts): migrate from SmartNavLink to LinkComponentProvider'). The documentation's description does not match the project's actual activity.
|
|
13
|
-
Files: .claude/project-map.md
|
|
14
|
-
Action: Investigate the project's true nature. Update the project map description to reflect the actual tech stack (Django, Next.js, DRF Spectacular) or confirm the correct documentation is being analyzed.
|
|
15
|
-
(id: 63e451e12988)
|
|
16
|
-
|
|
17
|
-
- [?] The project map's 'Entry Points' list React component entry files (e.g., './index.tsx'), but commits suggest a Django/Next.js application with different entry points (e.g., Django's manage.py, Next.js's pages/).
|
|
18
|
-
Files: .claude/project-map.md
|
|
19
|
-
Action: Update the entry points list in the project map to reflect the actual application entry points (e.g., main Django ASGI/WSGI file, Next.js entry).
|
|
20
|
-
(id: ba6f809d9f15)
|
|
21
|
-
|
|
22
|
-
## Missing Documentation
|
|
23
|
-
|
|
24
|
-
- [!] The project map lists source directories (components, context, hooks, utils) typical of a React library, but recent commits mention backend (Django) and frontend framework (Next.js) concepts not covered. Key areas like 'layouts/', 'navigation/', and Django app structure have no documentation in the provided map.
|
|
25
|
-
Files: .claude/project-map.md
|
|
26
|
-
Action: Expand the project map to document the actual codebase structure, including Django apps, Next.js pages/layouts, and how the frontend and backend integrate.
|
|
27
|
-
(id: 4654646dd66d)
|
|
28
|
-
|
|
29
|
-
- [!] No dependencies are listed in the analysis, but recent commits reference specific packages and tools (e.g., 'drf-spectacular', 'Nextra'). The project map provides no dependency information, which is a critical gap for understanding the project's environment.
|
|
30
|
-
Files: .claude/project-map.md
|
|
31
|
-
Action: Add a 'Dependencies' section to the project map or ensure dependency files (pyproject.toml, package.json) are included in the documentation analysis.
|
|
32
|
-
(id: c81e59d10f1a)
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
Model: deepseek/deepseek-v3.2 | Tokens: 1941
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
context_files:
|
|
3
|
-
- .claude/project-map.md
|
|
4
|
-
created_at: '2026-04-22T16:47:48.100263Z'
|
|
5
|
-
id: T-001
|
|
6
|
-
priority: high
|
|
7
|
-
source: sidecar_review
|
|
8
|
-
source_item_id: 63e451e12988
|
|
9
|
-
status: pending
|
|
10
|
-
title: '[contradiction] The project map describes a React component library for an
|
|
11
|
-
OpenAPI viewer, but r'
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
The project map describes a React component library for an OpenAPI viewer, but recent git commits are for a Django/Next.js project (e.g., 'feat(drf-spectacular)', 'refactor(layouts): migrate from SmartNavLink to LinkComponentProvider'). The documentation's description does not match the project's actual activity.
|
|
15
|
-
|
|
16
|
-
**Files:** .claude/project-map.md
|
|
17
|
-
|
|
18
|
-
**Action:** Investigate the project's true nature. Update the project map description to reflect the actual tech stack (Django, Next.js, DRF Spectacular) or confirm the correct documentation is being analyzed.
|