@leanspec/ui 0.2.6-dev.20251125070417 → 0.2.6-dev.20251125092039

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 (112) hide show
  1. package/.next/standalone/packages/ui/.next/BUILD_ID +1 -1
  2. package/.next/standalone/packages/ui/.next/build-manifest.json +2 -2
  3. package/.next/standalone/packages/ui/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/packages/ui/.next/server/app/_global-error.html +2 -2
  5. package/.next/standalone/packages/ui/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  7. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  8. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  9. package/.next/standalone/packages/ui/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/standalone/packages/ui/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/.next/standalone/packages/ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/packages/ui/.next/server/app/_not-found.html +2 -2
  13. package/.next/standalone/packages/ui/.next/server/app/_not-found.rsc +17 -17
  14. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
  15. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  16. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +3 -3
  17. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  18. package/.next/standalone/packages/ui/.next/server/app/_not-found.segments/_tree.segment.rsc +5 -5
  19. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/route.js.nft.json +1 -1
  20. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/[spec]/route.js.nft.json +1 -1
  21. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/specs/route.js.nft.json +1 -1
  22. package/.next/standalone/packages/ui/.next/server/app/api/projects/[id]/stats/route.js.nft.json +1 -1
  23. package/.next/standalone/packages/ui/.next/server/app/api/projects/route.js.nft.json +1 -1
  24. package/.next/standalone/packages/ui/.next/server/app/api/revalidate/route.js.nft.json +1 -1
  25. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/dependency-graph/route.js.nft.json +1 -1
  26. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/route.js.nft.json +1 -1
  27. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/status/route.js.nft.json +1 -1
  28. package/.next/standalone/packages/ui/.next/server/app/api/specs/[id]/subspecs/[file]/route.js.nft.json +1 -1
  29. package/.next/standalone/packages/ui/.next/server/app/api/stats/route.js.nft.json +1 -1
  30. package/.next/standalone/packages/ui/.next/server/app/page.js.nft.json +1 -1
  31. package/.next/standalone/packages/ui/.next/server/app/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page.js.nft.json +1 -1
  33. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page.js.nft.json +1 -1
  35. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/[specId]/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page.js.nft.json +1 -1
  37. package/.next/standalone/packages/ui/.next/server/app/projects/[projectId]/specs/page_client-reference-manifest.js +1 -1
  38. package/.next/standalone/packages/ui/.next/server/app/projects/page.js.nft.json +1 -1
  39. package/.next/standalone/packages/ui/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/packages/ui/.next/server/app/projects.html +2 -2
  41. package/.next/standalone/packages/ui/.next/server/app/projects.rsc +11 -11
  42. package/.next/standalone/packages/ui/.next/server/app/projects.segments/_full.segment.rsc +11 -11
  43. package/.next/standalone/packages/ui/.next/server/app/projects.segments/_index.segment.rsc +9 -9
  44. package/.next/standalone/packages/ui/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
  45. package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects/__PAGE__.segment.rsc +2 -2
  46. package/.next/standalone/packages/ui/.next/server/app/projects.segments/projects.segment.rsc +1 -1
  47. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page.js.nft.json +1 -1
  48. package/.next/standalone/packages/ui/.next/server/app/specs/[id]/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/packages/ui/.next/server/app/specs/page.js.nft.json +1 -1
  50. package/.next/standalone/packages/ui/.next/server/app/specs/page_client-reference-manifest.js +1 -1
  51. package/.next/standalone/packages/ui/.next/server/app/stats/page.js.nft.json +1 -1
  52. package/.next/standalone/packages/ui/.next/server/app/stats/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/packages/ui/.next/server/chunks/ssr/[root-of-the-server]__1d0c2012._.js +1 -1
  54. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_14118969._.js +3 -0
  55. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_497c8b73._.js +3 -0
  56. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_c2f54661._.js +1 -1
  57. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_app_specs_specs-client_tsx_0bb8f8f8._.js +1 -1
  58. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_spec-detail-loading-shell_tsx_f7136fb6._.js +3 -0
  59. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_specs-nav-sidebar_tsx_8237ed13._.js +1 -1
  60. package/.next/standalone/packages/ui/.next/server/chunks/ssr/packages_ui_src_components_ui_select_tsx_7e21d40f._.js +1 -1
  61. package/.next/standalone/packages/ui/.next/server/pages/404.html +2 -2
  62. package/.next/standalone/packages/ui/.next/server/pages/500.html +2 -2
  63. package/.next/standalone/packages/ui/.next/server/server-reference-manifest.js +1 -1
  64. package/.next/standalone/packages/ui/.next/server/server-reference-manifest.json +1 -1
  65. package/.next/standalone/packages/ui/.next/static/chunks/15ea4a571afd2593.css +1 -0
  66. package/.next/standalone/packages/ui/.next/static/chunks/{a055d4b2866ff8d4.js → 1c1c8333e0a19d47.js} +1 -1
  67. package/.next/standalone/packages/ui/.next/static/chunks/1fe09300b3bcaa23.js +1 -0
  68. package/.next/standalone/packages/ui/.next/static/chunks/3f4207bc02becc30.js +1 -0
  69. package/.next/{static/chunks/afb7c5a4255f4081.js → standalone/packages/ui/.next/static/chunks/43bc30cd222b1e74.js} +1 -1
  70. package/.next/standalone/packages/ui/.next/static/chunks/5f0014bb843d2f45.js +1 -0
  71. package/.next/standalone/packages/ui/.next/static/chunks/{17471c87082c392c.js → 86bf3f94f2b0b8fc.js} +2 -2
  72. package/.next/standalone/packages/ui/.next/static/chunks/96339d2504976e86.js +1 -0
  73. package/.next/standalone/packages/ui/.next/static/chunks/d4de5562fb8f6e76.js +1 -0
  74. package/.next/standalone/packages/ui/leanspec.db +0 -0
  75. package/.next/standalone/packages/ui/package.json +1 -1
  76. package/.next/standalone/packages/ui/src/app/globals.css +25 -0
  77. package/.next/standalone/packages/ui/src/app/specs/specs-client.tsx +180 -140
  78. package/.next/standalone/packages/ui/src/components/main-sidebar.tsx +3 -3
  79. package/.next/standalone/packages/ui/src/components/spec-detail-client.tsx +25 -7
  80. package/.next/standalone/packages/ui/src/components/specs-nav-sidebar.tsx +3 -13
  81. package/.next/standalone/packages/ui/tsconfig.tsbuildinfo +1 -1
  82. package/.next/static/chunks/15ea4a571afd2593.css +1 -0
  83. package/.next/static/chunks/{a055d4b2866ff8d4.js → 1c1c8333e0a19d47.js} +1 -1
  84. package/.next/static/chunks/1fe09300b3bcaa23.js +1 -0
  85. package/.next/static/chunks/3f4207bc02becc30.js +1 -0
  86. package/.next/{standalone/packages/ui/.next/static/chunks/afb7c5a4255f4081.js → static/chunks/43bc30cd222b1e74.js} +1 -1
  87. package/.next/static/chunks/5f0014bb843d2f45.js +1 -0
  88. package/.next/static/chunks/{17471c87082c392c.js → 86bf3f94f2b0b8fc.js} +2 -2
  89. package/.next/static/chunks/96339d2504976e86.js +1 -0
  90. package/.next/static/chunks/d4de5562fb8f6e76.js +1 -0
  91. package/package.json +1 -1
  92. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_196b5a83._.js +0 -3
  93. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_6e4dd8b6._.js +0 -3
  94. package/.next/standalone/packages/ui/.next/server/chunks/ssr/_f1e82b43._.js +0 -3
  95. package/.next/standalone/packages/ui/.next/static/chunks/35ccd3286e538bd2.js +0 -1
  96. package/.next/standalone/packages/ui/.next/static/chunks/5a6fc30cd64085b4.css +0 -1
  97. package/.next/standalone/packages/ui/.next/static/chunks/843e1dfdc28e1385.js +0 -1
  98. package/.next/standalone/packages/ui/.next/static/chunks/9dad7fd31627df80.js +0 -1
  99. package/.next/standalone/packages/ui/.next/static/chunks/f9db24028d8c058e.js +0 -1
  100. package/.next/standalone/packages/ui/.next/static/chunks/fb640f50455ba34f.js +0 -1
  101. package/.next/static/chunks/35ccd3286e538bd2.js +0 -1
  102. package/.next/static/chunks/5a6fc30cd64085b4.css +0 -1
  103. package/.next/static/chunks/843e1dfdc28e1385.js +0 -1
  104. package/.next/static/chunks/9dad7fd31627df80.js +0 -1
  105. package/.next/static/chunks/f9db24028d8c058e.js +0 -1
  106. package/.next/static/chunks/fb640f50455ba34f.js +0 -1
  107. /package/.next/standalone/packages/ui/.next/static/{X4lxx4RWGwS4tCD7M7u1r → XaKCij6ONZQYRwoNl_cx7}/_buildManifest.js +0 -0
  108. /package/.next/standalone/packages/ui/.next/static/{X4lxx4RWGwS4tCD7M7u1r → XaKCij6ONZQYRwoNl_cx7}/_clientMiddlewareManifest.json +0 -0
  109. /package/.next/standalone/packages/ui/.next/static/{X4lxx4RWGwS4tCD7M7u1r → XaKCij6ONZQYRwoNl_cx7}/_ssgManifest.js +0 -0
  110. /package/.next/static/{X4lxx4RWGwS4tCD7M7u1r → XaKCij6ONZQYRwoNl_cx7}/_buildManifest.js +0 -0
  111. /package/.next/static/{X4lxx4RWGwS4tCD7M7u1r → XaKCij6ONZQYRwoNl_cx7}/_clientMiddlewareManifest.json +0 -0
  112. /package/.next/static/{X4lxx4RWGwS4tCD7M7u1r → XaKCij6ONZQYRwoNl_cx7}/_ssgManifest.js +0 -0
@@ -26,7 +26,9 @@ import {
26
26
  FileText,
27
27
  GitBranch,
28
28
  Maximize2,
29
- Minimize2
29
+ Minimize2,
30
+ ChevronDown,
31
+ ChevronRight
30
32
  } from 'lucide-react';
31
33
  import { StatusBadge } from '@/components/status-badge';
32
34
  import { PriorityBadge } from '@/components/priority-badge';
@@ -254,41 +256,44 @@ export function SpecsClient({ initialSpecs, projectId }: SpecsClientProps) {
254
256
  }, [specs, searchQuery, statusFilter, priorityFilter, sortBy, viewMode]);
255
257
 
256
258
  return (
257
- <div className="h-[calc(100vh-3.5rem)] flex flex-col overflow-hidden bg-background p-4">
259
+ <div className="h-[calc(100vh-3.5rem)] flex flex-col overflow-hidden bg-background p-2 sm:p-4">
258
260
  <div className={cn(
259
261
  "flex flex-col h-full mx-auto transition-all duration-300",
260
262
  isWideMode ? "w-full" : "max-w-7xl w-full"
261
263
  )}>
262
264
  {/* Unified Compact Header */}
263
- <div className="flex-none mb-4">
264
- <div className="flex flex-col gap-4">
265
- <div className="flex items-center justify-between gap-4">
266
- <div>
267
- <h1 className="text-2xl font-bold tracking-tight">Specifications</h1>
268
- <p className="text-sm text-muted-foreground">
265
+ <div className="flex-none mb-3 sm:mb-4">
266
+ <div className="flex flex-col gap-2 sm:gap-3 md:gap-4">
267
+ {/* Title and Controls Row */}
268
+ <div className="flex items-center justify-between gap-2">
269
+ <div className="min-w-0">
270
+ <h1 className="text-lg sm:text-xl md:text-2xl font-bold tracking-tight truncate">Specifications</h1>
271
+ <p className="text-xs sm:text-sm text-muted-foreground">
269
272
  {filteredAndSortedSpecs.length} specs
270
273
  </p>
271
274
  </div>
272
275
 
273
- <div className="flex items-center gap-2">
274
- <div className="flex items-center gap-2 bg-muted/50 p-1 rounded-lg">
276
+ <div className="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
277
+ <div className="flex items-center gap-0.5 sm:gap-1 bg-muted/50 p-0.5 sm:p-1 rounded-lg">
275
278
  <Button
276
279
  variant={viewMode === 'list' ? 'secondary' : 'ghost'}
277
280
  size="sm"
278
281
  onClick={() => setViewMode('list')}
279
- className="h-8 px-2 lg:px-3"
282
+ className="h-7 sm:h-8 px-2 sm:px-3"
283
+ title="List view"
280
284
  >
281
- <ListIcon className="h-4 w-4 lg:mr-2" />
282
- <span className="hidden lg:inline">List</span>
285
+ <ListIcon className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
286
+ <span className="hidden lg:inline ml-2">List</span>
283
287
  </Button>
284
288
  <Button
285
289
  variant={viewMode === 'board' ? 'secondary' : 'ghost'}
286
290
  size="sm"
287
291
  onClick={() => setViewMode('board')}
288
- className="h-8 px-2 lg:px-3"
292
+ className="h-7 sm:h-8 px-2 sm:px-3"
293
+ title="Board view"
289
294
  >
290
- <LayoutGrid className="h-4 w-4 lg:mr-2" />
291
- <span className="hidden lg:inline">Board</span>
295
+ <LayoutGrid className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
296
+ <span className="hidden lg:inline ml-2">Board</span>
292
297
  </Button>
293
298
  </div>
294
299
 
@@ -296,62 +301,67 @@ export function SpecsClient({ initialSpecs, projectId }: SpecsClientProps) {
296
301
  variant="ghost"
297
302
  size="icon"
298
303
  onClick={() => setIsWideMode(!isWideMode)}
299
- className="h-10 w-10 text-muted-foreground hover:text-foreground"
304
+ className="hidden md:flex h-8 w-8 sm:h-10 sm:w-10 text-muted-foreground hover:text-foreground"
300
305
  title={isWideMode ? "Exit wide mode" : "Enter wide mode"}
301
306
  >
302
- {isWideMode ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
307
+ {isWideMode ? <Minimize2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" /> : <Maximize2 className="h-3.5 w-3.5 sm:h-4 sm:w-4" />}
303
308
  </Button>
304
309
  </div>
305
310
  </div>
306
311
 
307
- <div className="flex items-center gap-2 overflow-x-auto pb-2">
308
- <div className="relative flex-1 min-w-[200px] max-w-md">
309
- <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
312
+ {/* Search and Filters Row */}
313
+ <div className="flex flex-col gap-2">
314
+ {/* Search Bar - Full width on mobile */}
315
+ <div className="relative w-full">
316
+ <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 sm:h-4 sm:w-4 text-muted-foreground pointer-events-none" />
310
317
  <Input
311
318
  placeholder="Search specs..."
312
319
  value={searchQuery}
313
320
  onChange={(e) => setSearchQuery(e.target.value)}
314
- className="pl-9 h-9"
321
+ className="pl-8 sm:pl-9 h-9 sm:h-10 w-full text-sm"
315
322
  />
316
323
  </div>
317
324
 
318
- <Select value={statusFilter} onValueChange={(value) => setStatusFilter(value as SpecStatus | 'all')}>
319
- <SelectTrigger className="w-[140px] h-9">
320
- <SelectValue placeholder="Status" />
321
- </SelectTrigger>
322
- <SelectContent>
323
- <SelectItem value="all">All Status</SelectItem>
324
- <SelectItem value="planned">Planned</SelectItem>
325
- <SelectItem value="in-progress">In Progress</SelectItem>
326
- <SelectItem value="complete">Complete</SelectItem>
327
- <SelectItem value="archived">Archived</SelectItem>
328
- </SelectContent>
329
- </Select>
330
-
331
- <Select value={priorityFilter} onValueChange={setPriorityFilter}>
332
- <SelectTrigger className="w-[140px] h-9">
333
- <SelectValue placeholder="Priority" />
334
- </SelectTrigger>
335
- <SelectContent>
336
- <SelectItem value="all">All Priority</SelectItem>
337
- <SelectItem value="critical">Critical</SelectItem>
338
- <SelectItem value="high">High</SelectItem>
339
- <SelectItem value="medium">Medium</SelectItem>
340
- <SelectItem value="low">Low</SelectItem>
341
- </SelectContent>
342
- </Select>
343
-
344
- <Select value={sortBy} onValueChange={(v) => setSortBy(v as SortBy)}>
345
- <SelectTrigger className="w-[180px] h-9">
346
- <SelectValue placeholder="Sort by" />
347
- </SelectTrigger>
348
- <SelectContent>
349
- <SelectItem value="id-desc">Newest First</SelectItem>
350
- <SelectItem value="id-asc">Oldest First</SelectItem>
351
- <SelectItem value="updated-desc">Recently Updated</SelectItem>
352
- <SelectItem value="title-asc">Title (A-Z)</SelectItem>
353
- </SelectContent>
354
- </Select>
325
+ {/* Filters - Horizontal scroll on mobile with snap points */}
326
+ <div className="flex items-center gap-2 overflow-x-auto snap-x snap-mandatory pb-1 -mx-2 px-2 sm:mx-0 sm:px-0 sm:pb-0 scrollbar-thin">
327
+ <Select value={statusFilter} onValueChange={(value) => setStatusFilter(value as SpecStatus | 'all')}>
328
+ <SelectTrigger className="w-[110px] sm:w-[130px] h-9 sm:h-10 flex-shrink-0 snap-start text-xs sm:text-sm">
329
+ <SelectValue placeholder="Status" />
330
+ </SelectTrigger>
331
+ <SelectContent>
332
+ <SelectItem value="all">All Status</SelectItem>
333
+ <SelectItem value="planned">Planned</SelectItem>
334
+ <SelectItem value="in-progress">In Progress</SelectItem>
335
+ <SelectItem value="complete">Complete</SelectItem>
336
+ <SelectItem value="archived">Archived</SelectItem>
337
+ </SelectContent>
338
+ </Select>
339
+
340
+ <Select value={priorityFilter} onValueChange={setPriorityFilter}>
341
+ <SelectTrigger className="w-[110px] sm:w-[130px] h-9 sm:h-10 flex-shrink-0 snap-start text-xs sm:text-sm">
342
+ <SelectValue placeholder="Priority" />
343
+ </SelectTrigger>
344
+ <SelectContent>
345
+ <SelectItem value="all">All Priority</SelectItem>
346
+ <SelectItem value="critical">Critical</SelectItem>
347
+ <SelectItem value="high">High</SelectItem>
348
+ <SelectItem value="medium">Medium</SelectItem>
349
+ <SelectItem value="low">Low</SelectItem>
350
+ </SelectContent>
351
+ </Select>
352
+
353
+ <Select value={sortBy} onValueChange={(v) => setSortBy(v as SortBy)}>
354
+ <SelectTrigger className="w-[130px] sm:w-[170px] h-9 sm:h-10 flex-shrink-0 snap-start text-xs sm:text-sm">
355
+ <SelectValue placeholder="Sort by" />
356
+ </SelectTrigger>
357
+ <SelectContent>
358
+ <SelectItem value="id-desc">Newest First</SelectItem>
359
+ <SelectItem value="id-asc">Oldest First</SelectItem>
360
+ <SelectItem value="updated-desc">Recently Updated</SelectItem>
361
+ <SelectItem value="title-asc">Title (A-Z)</SelectItem>
362
+ </SelectContent>
363
+ </Select>
364
+ </div>
355
365
  </div>
356
366
  </div>
357
367
  </div>
@@ -382,7 +392,7 @@ export function SpecsClient({ initialSpecs, projectId }: SpecsClientProps) {
382
392
 
383
393
  function ListView({ specs }: { specs: Spec[] }) {
384
394
  return (
385
- <div className="grid grid-cols-1 gap-4 pb-8">
395
+ <div className="grid grid-cols-1 gap-2 sm:gap-3 md:gap-4 pb-2 sm:pb-4 md:pb-8">
386
396
  {specs.map(spec => {
387
397
  const priorityColors = {
388
398
  'critical': 'border-l-red-500',
@@ -398,78 +408,83 @@ function ListView({ specs }: { specs: Spec[] }) {
398
408
  <Card
399
409
  key={spec.id}
400
410
  className={cn(
401
- "hover:shadow-lg transition-all duration-150 hover:scale-[1.01] border-l-4 cursor-pointer",
411
+ "hover:shadow-lg active:shadow-xl transition-all duration-150 hover:scale-[1.01] active:scale-[0.99] border-l-4 cursor-pointer touch-manipulation",
402
412
  borderColor
403
413
  )}
404
414
  onClick={() => window.location.href = `/specs/${spec.specNumber || spec.id}`}
405
415
  >
406
- <CardHeader className="pb-3">
407
- <div className="flex items-start justify-between gap-4">
416
+ {/* Mobile-optimized layout */}
417
+ <CardHeader className="pb-2 sm:pb-2.5 md:pb-3 px-3 sm:px-4 md:px-6 pt-3 sm:pt-4 md:pt-6">
418
+ <div className="flex flex-col gap-2 md:flex-row md:items-start md:justify-between md:gap-4">
408
419
  <div className="flex-1 min-w-0">
409
420
  <Link href={`/specs/${spec.specNumber || spec.id}`}>
410
- <CardTitle className="text-lg font-semibold hover:text-primary transition-colors flex items-center">
421
+ <CardTitle className="text-sm sm:text-base md:text-lg font-semibold hover:text-primary transition-colors flex items-start flex-wrap gap-1.5 sm:gap-2 leading-snug sm:leading-normal">
411
422
  {spec.specNumber ? (
412
- <span className="font-mono text-base font-normal text-muted-foreground mr-3">
423
+ <span className="font-mono text-xs sm:text-sm md:text-base font-normal text-muted-foreground flex-shrink-0">
413
424
  #{spec.specNumber.toString().padStart(3, '0')}
414
425
  </span>
415
426
  ) : null}
416
- {spec.title || spec.specName}
427
+ <span className="flex-1 break-words">{spec.title || spec.specName}</span>
417
428
  </CardTitle>
418
429
  </Link>
419
430
  {spec.title && spec.title !== spec.specName && (
420
- <p className="text-xs font-mono text-muted-foreground mt-1.5 truncate">{spec.specName}</p>
431
+ <p className="text-[11px] sm:text-xs font-mono text-muted-foreground mt-1 sm:mt-1.5 truncate">{spec.specName}</p>
421
432
  )}
422
433
  </div>
423
- <div className="flex gap-2 shrink-0">
434
+ {/* Badges: responsive layout */}
435
+ <div className="flex gap-1.5 sm:gap-2 flex-wrap md:shrink-0">
424
436
  {spec.status && <StatusBadge status={spec.status} />}
425
437
  {spec.priority && <PriorityBadge priority={spec.priority} />}
426
438
  </div>
427
439
  </div>
428
440
  </CardHeader>
429
441
 
430
- <CardContent className="flex items-center justify-between gap-4 pt-0">
431
- {/* Metadata (Left) */}
432
- <div className="flex items-center gap-4 text-sm text-muted-foreground flex-wrap">
433
- {(spec.updatedAt || hasSubSpecs || hasDependencies) ? (
434
- <>
435
- {spec.updatedAt && (
436
- <div className="flex items-center gap-1.5">
437
- <Clock className="h-3.5 w-3.5" />
438
- <span>Updated {formatRelativeTime(spec.updatedAt)}</span>
439
- </div>
440
- )}
441
- {hasSubSpecs && (
442
- <div className="flex items-center gap-1.5">
443
- <FileText className="h-3.5 w-3.5" />
444
- <span>+{spec.subSpecsCount} files</span>
445
- </div>
446
- )}
447
- {hasDependencies && (
448
- <div className="flex items-center gap-1.5">
449
- <GitBranch className="h-3.5 w-3.5" />
450
- <span>
451
- {spec.relationships!.dependsOn.length > 0 && `${spec.relationships!.dependsOn.length} deps`}
452
- {spec.relationships!.dependsOn.length > 0 && spec.relationships!.related.length > 0 && ', '}
453
- {spec.relationships!.related.length > 0 && `${spec.relationships!.related.length} related`}
454
- </span>
455
- </div>
456
- )}
457
- </>
458
- ) : (
459
- <span className="invisible">No metadata</span> /* Keep height consistent */
442
+ <CardContent className="px-3 sm:px-4 md:px-6 pb-2.5 sm:pb-3 md:pb-6 pt-0">
443
+ {/* Metadata and Tags - Stack on mobile */}
444
+ <div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between md:gap-4">
445
+ {/* Metadata (Left) */}
446
+ <div className="flex items-center gap-2 sm:gap-3 md:gap-4 text-[11px] sm:text-xs md:text-sm text-muted-foreground flex-wrap">
447
+ {(spec.updatedAt || hasSubSpecs || hasDependencies) ? (
448
+ <>
449
+ {spec.updatedAt && (
450
+ <div className="flex items-center gap-1 sm:gap-1.5">
451
+ <Clock className="h-3 w-3 sm:h-3.5 sm:w-3.5 flex-shrink-0" />
452
+ <span className="whitespace-nowrap">Updated {formatRelativeTime(spec.updatedAt)}</span>
453
+ </div>
454
+ )}
455
+ {hasSubSpecs && (
456
+ <div className="flex items-center gap-1 sm:gap-1.5">
457
+ <FileText className="h-3 w-3 sm:h-3.5 sm:w-3.5 flex-shrink-0" />
458
+ <span className="whitespace-nowrap">+{spec.subSpecsCount} files</span>
459
+ </div>
460
+ )}
461
+ {hasDependencies && (
462
+ <div className="flex items-center gap-1 sm:gap-1.5">
463
+ <GitBranch className="h-3 w-3 sm:h-3.5 sm:w-3.5 flex-shrink-0" />
464
+ <span className="whitespace-nowrap">
465
+ {spec.relationships!.dependsOn.length > 0 && `${spec.relationships!.dependsOn.length} deps`}
466
+ {spec.relationships!.dependsOn.length > 0 && spec.relationships!.related.length > 0 && ', '}
467
+ {spec.relationships!.related.length > 0 && `${spec.relationships!.related.length} related`}
468
+ </span>
469
+ </div>
470
+ )}
471
+ </>
472
+ ) : (
473
+ <span className="invisible hidden md:inline">No metadata</span> /* Keep height consistent on desktop */
474
+ )}
475
+ </div>
476
+
477
+ {/* Tags (Right on desktop, below on mobile) */}
478
+ {spec.tags && spec.tags.length > 0 && (
479
+ <div className="flex flex-wrap gap-1 sm:gap-1.5 md:gap-2 md:justify-end md:shrink-0">
480
+ {spec.tags.map(tag => (
481
+ <Badge key={tag} variant="outline" className="text-[10px] sm:text-xs font-mono text-muted-foreground hover:text-foreground transition-colors h-5 sm:h-auto px-1.5 sm:px-2">
482
+ {tag}
483
+ </Badge>
484
+ ))}
485
+ </div>
460
486
  )}
461
487
  </div>
462
-
463
- {/* Tags (Right) */}
464
- {spec.tags && spec.tags.length > 0 && (
465
- <div className="flex flex-wrap gap-2 justify-end shrink-0">
466
- {spec.tags.map(tag => (
467
- <Badge key={tag} variant="outline" className="text-xs font-mono text-muted-foreground hover:text-foreground transition-colors">
468
- {tag}
469
- </Badge>
470
- ))}
471
- </div>
472
- )}
473
488
  </CardContent>
474
489
  </Card>
475
490
  );
@@ -489,6 +504,14 @@ interface BoardViewProps {
489
504
  function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onToggleArchived }: BoardViewProps) {
490
505
  const [draggingId, setDraggingId] = useState<string | null>(null);
491
506
  const [activeDropZone, setActiveDropZone] = useState<SpecStatus | null>(null);
507
+ const [collapsedColumns, setCollapsedColumns] = useState<Record<string, boolean>>({});
508
+
509
+ const toggleColumn = (status: string) => {
510
+ setCollapsedColumns(prev => ({
511
+ ...prev,
512
+ [status]: !prev[status]
513
+ }));
514
+ };
492
515
 
493
516
  const columns = useMemo(() => {
494
517
  // Always show all columns, including archived (it will be rendered as collapsed bar when showArchived=false)
@@ -548,51 +571,68 @@ function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onTogg
548
571
  }, [draggingId, handleDragEnd, onStatusChange, specLookup]);
549
572
 
550
573
  return (
551
- <div className="flex gap-6 h-full pb-2">
574
+ <div className="flex flex-col md:flex-row gap-3 sm:gap-4 md:gap-6 h-full pb-2 md:snap-x md:snap-mandatory overflow-y-auto md:overflow-y-hidden md:overflow-x-auto">
552
575
  {columns.map(column => {
553
576
  const Icon = column.config.icon;
554
577
  const isArchivedColumn = column.status === 'archived';
578
+ const isCollapsed = collapsedColumns[column.status];
555
579
 
556
580
  return (
557
581
  <div key={column.status} className={cn(
558
- "flex flex-col h-full flex-1 min-w-[280px]",
559
- isArchivedColumn && !showArchived && "w-14 min-w-[3.5rem] flex-none flex-shrink-0"
582
+ "flex flex-col flex-1 snap-start",
583
+ "h-auto md:h-full w-full md:w-auto flex-shrink-0",
584
+ isArchivedColumn && !showArchived ? "md:w-12 md:sm:w-14 md:min-w-[3rem] md:sm:min-w-[3.5rem] flex-none" : "md:min-w-[260px] md:sm:min-w-[280px] md:md:min-w-[300px]"
560
585
  )}>
561
586
  <div className={cn(
562
- 'flex-none mb-4 rounded-lg border-2 bg-background transition-all',
587
+ 'flex-none mb-3 sm:mb-4 rounded-lg border-2 bg-background transition-all touch-manipulation',
563
588
  column.config.bgClass,
564
589
  column.config.borderClass,
565
- isArchivedColumn ? 'cursor-pointer hover:opacity-80' : '',
566
- isArchivedColumn && !showArchived ? 'py-6 px-2' : 'p-3'
590
+ isArchivedColumn ? 'cursor-pointer hover:opacity-80 active:opacity-70' : '',
591
+ isArchivedColumn && !showArchived ? 'py-4 sm:py-6 px-1.5 sm:px-2' : 'p-2.5 sm:p-3',
592
+ // Mobile collapsible header styling
593
+ 'md:cursor-default cursor-pointer'
567
594
  )}
568
- onClick={isArchivedColumn ? onToggleArchived : undefined}
595
+ onClick={() => {
596
+ if (isArchivedColumn) {
597
+ onToggleArchived();
598
+ } else {
599
+ // Only toggle collapse on mobile
600
+ if (window.innerWidth < 768) {
601
+ toggleColumn(column.status);
602
+ }
603
+ }
604
+ }}
569
605
  >
570
606
  <h2 className={cn(
571
- 'text-lg font-semibold flex items-center gap-2',
607
+ 'text-base sm:text-lg font-semibold flex items-center gap-1.5 sm:gap-2',
572
608
  column.config.colorClass,
573
- isArchivedColumn && !showArchived && 'flex-col text-sm gap-3'
609
+ isArchivedColumn && !showArchived && 'flex-col text-xs sm:text-sm gap-2 sm:gap-3'
574
610
  )}>
575
- <Icon className="h-5 w-5" />
611
+ <Icon className="h-4 w-4 sm:h-5 sm:w-5 flex-shrink-0" />
576
612
  {isArchivedColumn && !showArchived ? (
577
613
  <>
578
- <span className="vertical-text text-sm whitespace-nowrap">
614
+ <span className="vertical-text text-xs sm:text-sm whitespace-nowrap">
579
615
  {column.config.title}
580
616
  </span>
581
- <Badge variant="outline" className="text-xs">{column.specs.length}</Badge>
617
+ <Badge variant="outline" className="text-[10px] sm:text-xs px-1 sm:px-2 h-4 sm:h-5">{column.specs.length}</Badge>
582
618
  </>
583
619
  ) : (
584
620
  <>
585
- {column.config.title}
586
- <Badge variant="outline" className="ml-auto">{column.specs.length}</Badge>
621
+ <span className="truncate flex-1">{column.config.title}</span>
622
+ <Badge variant="outline" className="text-[10px] sm:text-xs px-1.5 sm:px-2 h-4 sm:h-5 flex-shrink-0">{column.specs.length}</Badge>
623
+ {/* Mobile collapse indicator */}
624
+ <div className="md:hidden ml-2 text-muted-foreground/50">
625
+ {isCollapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
626
+ </div>
587
627
  </>
588
628
  )}
589
629
  </h2>
590
630
  </div>
591
631
 
592
- {(!isArchivedColumn || showArchived) && (
632
+ {(!isArchivedColumn || showArchived) && !isCollapsed && (
593
633
  <div
594
634
  className={cn(
595
- 'space-y-3 flex-1 rounded-xl border border-transparent p-1 transition-colors overflow-y-auto min-h-0',
635
+ 'space-y-2 sm:space-y-2.5 md:space-y-3 flex-1 rounded-xl border border-transparent p-1 transition-colors overflow-y-auto min-h-0 scrollbar-thin',
596
636
  draggingId && 'border-dashed border-muted-foreground/40',
597
637
  draggingId && activeDropZone === column.status && 'bg-muted/40 border-primary/50'
598
638
  )}
@@ -624,49 +664,49 @@ function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onTogg
624
664
  onDragEnd={handleDragEnd}
625
665
  aria-disabled={isUpdating}
626
666
  className={cn(
627
- 'relative hover:shadow-lg transition-all duration-150 hover:scale-[1.02] border-l-4 cursor-pointer group flex flex-col',
667
+ 'relative hover:shadow-lg active:shadow-xl transition-all duration-150 hover:scale-[1.02] active:scale-[0.99] border-l-4 cursor-pointer group flex flex-col touch-manipulation',
628
668
  borderColor,
629
669
  isUpdating && 'opacity-60 cursor-wait'
630
670
  )}
631
671
  onClick={() => window.location.href = `/specs/${spec.specNumber || spec.id}`}
632
672
  >
633
673
  {isUpdating && (
634
- <div className="absolute inset-0 rounded-lg bg-background/80 flex items-center justify-center text-xs font-medium z-10">
674
+ <div className="absolute inset-0 rounded-lg bg-background/80 flex items-center justify-center text-xs sm:text-sm font-medium z-10">
635
675
  Updating...
636
676
  </div>
637
677
  )}
638
- <CardHeader className="p-4 pb-2 space-y-1.5">
678
+ <CardHeader className="p-3 sm:p-4 pb-1.5 sm:pb-2 space-y-1 sm:space-y-1.5">
639
679
  <div className="flex items-center justify-between">
640
- <span className="font-mono text-xs text-muted-foreground/70 group-hover:text-primary/60 transition-colors">
680
+ <span className="font-mono text-[10px] sm:text-xs text-muted-foreground/70 group-hover:text-primary/60 transition-colors">
641
681
  {spec.specNumber ? `#${spec.specNumber}` : ''}
642
682
  </span>
643
683
  </div>
644
684
  <Link href={`/specs/${spec.specNumber || spec.id}`} className="block">
645
- <CardTitle className="text-sm font-semibold leading-snug hover:text-primary transition-colors line-clamp-3">
685
+ <CardTitle className="text-xs sm:text-sm font-semibold leading-snug hover:text-primary transition-colors line-clamp-3">
646
686
  {spec.title || spec.specName}
647
687
  </CardTitle>
648
688
  </Link>
649
689
  </CardHeader>
650
- <CardContent className="p-4 pt-2 flex-1 flex flex-col justify-end">
651
- <div className="flex flex-col gap-3">
690
+ <CardContent className="p-3 sm:p-4 pt-1.5 sm:pt-2 flex-1 flex flex-col justify-end">
691
+ <div className="flex flex-col gap-2 sm:gap-3">
652
692
  {spec.title && spec.title !== spec.specName && (
653
- <p className="text-xs font-mono text-muted-foreground truncate opacity-70">
693
+ <p className="text-[10px] sm:text-xs font-mono text-muted-foreground truncate opacity-70">
654
694
  {spec.specName}
655
695
  </p>
656
696
  )}
657
697
 
658
- <div className="flex items-center justify-between gap-2 pt-1">
698
+ <div className="flex items-center justify-between gap-1.5 sm:gap-2 pt-0.5 sm:pt-1">
659
699
  {spec.priority ? <PriorityBadge priority={spec.priority} /> : <div />}
660
700
 
661
701
  {spec.tags && spec.tags.length > 0 && (
662
- <div className="flex flex-wrap gap-1 justify-end">
702
+ <div className="flex flex-wrap gap-0.5 sm:gap-1 justify-end">
663
703
  {spec.tags.slice(0, 2).map(tag => (
664
- <Badge key={tag} variant="outline" className="text-[10px] px-1.5 h-5 font-mono text-muted-foreground/80">
704
+ <Badge key={tag} variant="outline" className="text-[9px] sm:text-[10px] px-1 sm:px-1.5 h-4 sm:h-5 font-mono text-muted-foreground/80">
665
705
  {tag}
666
706
  </Badge>
667
707
  ))}
668
708
  {spec.tags.length > 2 && (
669
- <Badge variant="outline" className="text-[10px] px-1.5 h-5 font-mono text-muted-foreground/80">
709
+ <Badge variant="outline" className="text-[9px] sm:text-[10px] px-1 sm:px-1.5 h-4 sm:h-5 font-mono text-muted-foreground/80">
670
710
  +{spec.tags.length - 2}
671
711
  </Badge>
672
712
  )}
@@ -681,9 +721,9 @@ function BoardView({ specs, onStatusChange, pendingSpecIds, showArchived, onTogg
681
721
 
682
722
  {column.specs.length === 0 && (
683
723
  <Card className="border-dashed border-gray-300 dark:border-gray-700 bg-transparent">
684
- <CardContent className="py-8 text-center">
685
- <Icon className={cn('mx-auto h-8 w-8 mb-2', column.config.colorClass, 'opacity-50')} />
686
- <p className="text-sm text-muted-foreground">Drop here to move specs</p>
724
+ <CardContent className="py-6 sm:py-8 text-center px-2">
725
+ <Icon className={cn('mx-auto h-6 w-6 sm:h-8 sm:w-8 mb-1.5 sm:mb-2', column.config.colorClass, 'opacity-50')} />
726
+ <p className="text-xs sm:text-sm text-muted-foreground">Drop here to move specs</p>
687
727
  </CardContent>
688
728
  </Card>
689
729
  )}
@@ -98,12 +98,12 @@ export function MainSidebar() {
98
98
  {/* Sidebar */}
99
99
  <aside
100
100
  className={cn(
101
- "sticky top-14 h-[calc(100vh-3.5rem)] border-r border-border bg-background transition-all duration-300 flex-shrink-0",
101
+ "border-r border-border bg-background transition-all duration-300 flex-shrink-0",
102
102
  // Desktop behavior
103
- "hidden lg:flex",
103
+ "hidden lg:flex lg:sticky lg:top-14 lg:h-[calc(100vh-3.5rem)]",
104
104
  mounted && isCollapsed ? "lg:w-[60px]" : "lg:w-[240px]",
105
105
  // Mobile behavior - show as overlay when open
106
- mobileOpen && "fixed left-0 top-14 z-50 flex w-[280px]"
106
+ mobileOpen && "fixed inset-y-0 left-0 z-[60] flex w-[280px]"
107
107
  )}
108
108
  >
109
109
  <div className="flex flex-col h-full w-full">
@@ -43,7 +43,8 @@ import {
43
43
  Home,
44
44
  TrendingUp,
45
45
  Clock,
46
- Maximize2
46
+ Maximize2,
47
+ List as ListIcon
47
48
  } from 'lucide-react';
48
49
  import type { Plugin } from 'unified';
49
50
  import { visit } from 'unist-util-visit';
@@ -216,12 +217,29 @@ export function SpecDetailClient({ initialSpec, initialSubSpec }: SpecDetailClie
216
217
  <header ref={headerRef} className="lg:sticky lg:top-14 lg:z-20 border-b bg-card">
217
218
  <div className="px-3 sm:px-6 py-3 sm:py-4">
218
219
  {/* Line 1: Spec number + H1 Title */}
219
- <h1 className="text-xl sm:text-2xl font-bold tracking-tight mb-2 sm:mb-3">
220
- {spec.specNumber && (
221
- <span className="text-muted-foreground">#{spec.specNumber.toString().padStart(3, '0')} </span>
222
- )}
223
- {displayTitle}
224
- </h1>
220
+ <div className="flex items-start justify-between gap-2 mb-2 sm:mb-3">
221
+ <h1 className="text-xl sm:text-2xl font-bold tracking-tight">
222
+ {spec.specNumber && (
223
+ <span className="text-muted-foreground">#{spec.specNumber.toString().padStart(3, '0')} </span>
224
+ )}
225
+ {displayTitle}
226
+ </h1>
227
+
228
+ {/* Mobile Specs List Toggle */}
229
+ <Button
230
+ variant="ghost"
231
+ size="icon"
232
+ className="lg:hidden h-8 w-8 -mr-2 shrink-0 text-muted-foreground"
233
+ onClick={() => {
234
+ if (typeof window !== 'undefined' && window.toggleSpecsSidebar) {
235
+ window.toggleSpecsSidebar();
236
+ }
237
+ }}
238
+ >
239
+ <ListIcon className="h-5 w-5" />
240
+ <span className="sr-only">Toggle specs list</span>
241
+ </Button>
242
+ </div>
225
243
 
226
244
  {/* Line 2: Status, Priority, Tags, Actions */}
227
245
  <div className="flex flex-wrap items-center gap-2">
@@ -395,12 +395,12 @@ export function SpecsNavSidebar({ initialSpecs = [], currentSpecId, currentSubSp
395
395
 
396
396
  <div className="relative flex-shrink-0">
397
397
  <aside className={cn(
398
- "sticky top-14 h-[calc(100vh-3.5rem)] border-r border-border bg-background flex flex-col overflow-hidden transition-all duration-300",
398
+ "border-r border-border bg-background flex flex-col overflow-hidden transition-all duration-300",
399
399
  // Desktop behavior
400
- "hidden lg:flex",
400
+ "hidden lg:flex lg:sticky lg:top-14 lg:h-[calc(100vh-3.5rem)]",
401
401
  mounted && isCollapsed ? "lg:w-0 lg:border-r-0" : "lg:w-[280px]",
402
402
  // Mobile behavior - show as overlay when open
403
- mobileOpen && "fixed left-0 top-14 z-50 flex w-[280px]"
403
+ mobileOpen && "fixed inset-y-0 left-0 z-[60] flex w-[280px]"
404
404
  )}>
405
405
  <div className="p-4 border-b border-border">
406
406
  <div className="flex items-center justify-between mb-3">
@@ -545,16 +545,6 @@ export function SpecsNavSidebar({ initialSpecs = [], currentSpecId, currentSubSp
545
545
  <ChevronRight className="h-4 w-4" />
546
546
  </Button>
547
547
  )}
548
-
549
- {/* Mobile floating toggle button - matches BackToTop/TOC style */}
550
- <Button
551
- onClick={() => setMobileOpen(true)}
552
- size="icon"
553
- className="lg:hidden fixed bottom-6 left-6 h-12 w-12 rounded-full shadow-lg z-40 hover:scale-110 transition-transform"
554
- aria-label="Show specifications list"
555
- >
556
- <ListIconLucide className="h-5 w-5" />
557
- </Button>
558
548
  </div>
559
549
  </TooltipProvider>
560
550
  );