@getcoherent/core 0.4.0 → 0.5.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sergei Kovtun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.ts CHANGED
@@ -1094,53 +1094,27 @@ declare const PageDefinitionSchema: z.ZodObject<{
1094
1094
  }>;
1095
1095
  type PageDefinition = z.infer<typeof PageDefinitionSchema>;
1096
1096
  /**
1097
- * Navigation item
1097
+ * Navigation item — supports flat links and grouped dropdown menus.
1098
+ * Items with the same `group` value are rendered as a DropdownMenu.
1099
+ * Items with `children` render as a dropdown trigger with sub-items.
1098
1100
  */
1099
- declare const NavigationItemSchema: z.ZodObject<{
1100
- label: z.ZodString;
1101
- route: z.ZodString;
1102
- icon: z.ZodOptional<z.ZodString>;
1103
- requiresAuth: z.ZodDefault<z.ZodBoolean>;
1104
- order: z.ZodNumber;
1105
- }, "strip", z.ZodTypeAny, {
1106
- order: number;
1107
- route: string;
1108
- requiresAuth: boolean;
1101
+ declare const NavigationItemSchema: z.ZodType<NavigationItem>;
1102
+ interface NavigationItem {
1109
1103
  label: string;
1110
- icon?: string | undefined;
1111
- }, {
1112
- order: number;
1113
1104
  route: string;
1114
- label: string;
1115
- requiresAuth?: boolean | undefined;
1116
- icon?: string | undefined;
1117
- }>;
1118
- type NavigationItem = z.infer<typeof NavigationItemSchema>;
1105
+ icon?: string;
1106
+ requiresAuth?: boolean;
1107
+ order: number;
1108
+ group?: string;
1109
+ children?: NavigationItem[];
1110
+ }
1119
1111
  /**
1120
1112
  * Navigation configuration
1121
1113
  */
1122
1114
  declare const NavigationSchema: z.ZodObject<{
1123
1115
  enabled: z.ZodDefault<z.ZodBoolean>;
1124
1116
  type: z.ZodDefault<z.ZodEnum<["header", "sidebar", "both"]>>;
1125
- items: z.ZodArray<z.ZodObject<{
1126
- label: z.ZodString;
1127
- route: z.ZodString;
1128
- icon: z.ZodOptional<z.ZodString>;
1129
- requiresAuth: z.ZodDefault<z.ZodBoolean>;
1130
- order: z.ZodNumber;
1131
- }, "strip", z.ZodTypeAny, {
1132
- order: number;
1133
- route: string;
1134
- requiresAuth: boolean;
1135
- label: string;
1136
- icon?: string | undefined;
1137
- }, {
1138
- order: number;
1139
- route: string;
1140
- label: string;
1141
- requiresAuth?: boolean | undefined;
1142
- icon?: string | undefined;
1143
- }>, "many">;
1117
+ items: z.ZodArray<z.ZodType<NavigationItem, z.ZodTypeDef, NavigationItem>, "many">;
1144
1118
  logo: z.ZodOptional<z.ZodObject<{
1145
1119
  text: z.ZodOptional<z.ZodString>;
1146
1120
  image: z.ZodOptional<z.ZodString>;
@@ -1154,25 +1128,13 @@ declare const NavigationSchema: z.ZodObject<{
1154
1128
  }, "strip", z.ZodTypeAny, {
1155
1129
  type: "header" | "sidebar" | "both";
1156
1130
  enabled: boolean;
1157
- items: {
1158
- order: number;
1159
- route: string;
1160
- requiresAuth: boolean;
1161
- label: string;
1162
- icon?: string | undefined;
1163
- }[];
1131
+ items: NavigationItem[];
1164
1132
  logo?: {
1165
1133
  text?: string | undefined;
1166
1134
  image?: string | undefined;
1167
1135
  } | undefined;
1168
1136
  }, {
1169
- items: {
1170
- order: number;
1171
- route: string;
1172
- label: string;
1173
- requiresAuth?: boolean | undefined;
1174
- icon?: string | undefined;
1175
- }[];
1137
+ items: NavigationItem[];
1176
1138
  type?: "header" | "sidebar" | "both" | undefined;
1177
1139
  enabled?: boolean | undefined;
1178
1140
  logo?: {
@@ -2058,25 +2020,7 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
2058
2020
  navigation: z.ZodOptional<z.ZodObject<{
2059
2021
  enabled: z.ZodDefault<z.ZodBoolean>;
2060
2022
  type: z.ZodDefault<z.ZodEnum<["header", "sidebar", "both"]>>;
2061
- items: z.ZodArray<z.ZodObject<{
2062
- label: z.ZodString;
2063
- route: z.ZodString;
2064
- icon: z.ZodOptional<z.ZodString>;
2065
- requiresAuth: z.ZodDefault<z.ZodBoolean>;
2066
- order: z.ZodNumber;
2067
- }, "strip", z.ZodTypeAny, {
2068
- order: number;
2069
- route: string;
2070
- requiresAuth: boolean;
2071
- label: string;
2072
- icon?: string | undefined;
2073
- }, {
2074
- order: number;
2075
- route: string;
2076
- label: string;
2077
- requiresAuth?: boolean | undefined;
2078
- icon?: string | undefined;
2079
- }>, "many">;
2023
+ items: z.ZodArray<z.ZodType<NavigationItem, z.ZodTypeDef, NavigationItem>, "many">;
2080
2024
  logo: z.ZodOptional<z.ZodObject<{
2081
2025
  text: z.ZodOptional<z.ZodString>;
2082
2026
  image: z.ZodOptional<z.ZodString>;
@@ -2090,25 +2034,13 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
2090
2034
  }, "strip", z.ZodTypeAny, {
2091
2035
  type: "header" | "sidebar" | "both";
2092
2036
  enabled: boolean;
2093
- items: {
2094
- order: number;
2095
- route: string;
2096
- requiresAuth: boolean;
2097
- label: string;
2098
- icon?: string | undefined;
2099
- }[];
2037
+ items: NavigationItem[];
2100
2038
  logo?: {
2101
2039
  text?: string | undefined;
2102
2040
  image?: string | undefined;
2103
2041
  } | undefined;
2104
2042
  }, {
2105
- items: {
2106
- order: number;
2107
- route: string;
2108
- label: string;
2109
- requiresAuth?: boolean | undefined;
2110
- icon?: string | undefined;
2111
- }[];
2043
+ items: NavigationItem[];
2112
2044
  type?: "header" | "sidebar" | "both" | undefined;
2113
2045
  enabled?: boolean | undefined;
2114
2046
  logo?: {
@@ -2436,13 +2368,7 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
2436
2368
  navigation?: {
2437
2369
  type: "header" | "sidebar" | "both";
2438
2370
  enabled: boolean;
2439
- items: {
2440
- order: number;
2441
- route: string;
2442
- requiresAuth: boolean;
2443
- label: string;
2444
- icon?: string | undefined;
2445
- }[];
2371
+ items: NavigationItem[];
2446
2372
  logo?: {
2447
2373
  text?: string | undefined;
2448
2374
  image?: string | undefined;
@@ -2628,13 +2554,7 @@ declare const DesignSystemConfigSchema: z.ZodObject<{
2628
2554
  autoScaffold?: boolean | undefined;
2629
2555
  };
2630
2556
  navigation?: {
2631
- items: {
2632
- order: number;
2633
- route: string;
2634
- label: string;
2635
- requiresAuth?: boolean | undefined;
2636
- icon?: string | undefined;
2637
- }[];
2557
+ items: NavigationItem[];
2638
2558
  type?: "header" | "sidebar" | "both" | undefined;
2639
2559
  enabled?: boolean | undefined;
2640
2560
  logo?: {
@@ -3491,6 +3411,11 @@ declare class PageGenerator {
3491
3411
  * Generate shared Footer component code for components/shared/footer.tsx.
3492
3412
  */
3493
3413
  generateSharedFooterCode(): string;
3414
+ /**
3415
+ * Generate shared Sidebar component code for components/shared/sidebar.tsx.
3416
+ * Used when navigation.type is 'sidebar' or 'both'.
3417
+ */
3418
+ generateSharedSidebarCode(): string;
3494
3419
  /**
3495
3420
  * Generate React SPA root layout
3496
3421
  */
@@ -3745,6 +3670,16 @@ declare class ProjectScaffolder {
3745
3670
  * Update config reference
3746
3671
  */
3747
3672
  updateConfig(newConfig: DesignSystemConfig): void;
3673
+ /**
3674
+ * Initial header for coherent init — Coherent Design Method branding.
3675
+ * Replaced by app-branded header on first `coherent chat` via regenerateLayout().
3676
+ */
3677
+ private generateInitialHeaderCode;
3678
+ /**
3679
+ * Initial footer for coherent init — Coherent Design Method branding.
3680
+ * Replaced by app-branded footer on first `coherent chat` via regenerateLayout().
3681
+ */
3682
+ private generateInitialFooterCode;
3748
3683
  }
3749
3684
 
3750
3685
  interface BasePageContent {
package/dist/index.js CHANGED
@@ -206,9 +206,10 @@ var NavigationItemSchema = z.object({
206
206
  label: z.string(),
207
207
  route: z.string(),
208
208
  icon: z.string().optional(),
209
- // lucide-react icon name
210
209
  requiresAuth: z.boolean().default(false),
211
- order: z.number()
210
+ order: z.number(),
211
+ group: z.string().optional(),
212
+ children: z.lazy(() => z.array(NavigationItemSchema)).optional()
212
213
  });
213
214
  var NavigationSchema = z.object({
214
215
  enabled: z.boolean().default(true),
@@ -3673,6 +3674,10 @@ const knownNames: Record<string, string> = {
3673
3674
  tooltip: 'Tooltip',
3674
3675
  'radio-group': 'RadioGroup',
3675
3676
  slider: 'Slider',
3677
+ sheet: 'Sheet',
3678
+ 'dropdown-menu': 'DropdownMenu',
3679
+ 'context-menu': 'ContextMenu',
3680
+ command: 'Command',
3676
3681
  }
3677
3682
 
3678
3683
  const descriptions: Record<string, string> = {
@@ -3697,6 +3702,10 @@ const descriptions: Record<string, string> = {
3697
3702
  tooltip: 'Contextual info on hover',
3698
3703
  'radio-group': 'Select one option from a set',
3699
3704
  slider: 'Select a value from a range',
3705
+ sheet: 'Slide-out panel from the edge of the screen',
3706
+ 'dropdown-menu': 'Menu triggered by a button with a list of actions',
3707
+ 'context-menu': 'Right-click menu with contextual actions',
3708
+ command: 'Command palette for searching and executing actions',
3700
3709
  }
3701
3710
 
3702
3711
  function displayName(component: any): string {
@@ -3848,6 +3857,147 @@ function SelectPreview({ size }: { size?: string }) {
3848
3857
  )
3849
3858
  }
3850
3859
 
3860
+ function SheetPreview({ side }: { side?: string }) {
3861
+ const [open, setOpen] = useState(false)
3862
+ const s = side || 'right'
3863
+ const positionClasses: Record<string, string> = {
3864
+ top: 'inset-x-0 top-0 border-b',
3865
+ bottom: 'inset-x-0 bottom-0 border-t',
3866
+ left: 'inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
3867
+ right: 'inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
3868
+ }
3869
+ return (
3870
+ <>
3871
+ <button onClick={() => setOpen(true)}
3872
+ className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 h-9 text-sm font-medium hover:bg-muted transition-colors">
3873
+ Open Sheet
3874
+ </button>
3875
+ {open && (
3876
+ <div className="fixed inset-0 z-50" onClick={() => setOpen(false)}>
3877
+ <div className="fixed inset-0 bg-black/80" />
3878
+ <div className={cn('fixed z-50 gap-4 bg-background p-6 shadow-lg', positionClasses[s] || positionClasses.right)}
3879
+ onClick={e => e.stopPropagation()}>
3880
+ <div className="flex flex-col space-y-2 mb-4">
3881
+ <h3 className="text-lg font-semibold">Sheet Panel</h3>
3882
+ <p className="text-sm text-muted-foreground">This is a sheet sliding from the {s}.</p>
3883
+ </div>
3884
+ <button onClick={() => setOpen(false)}
3885
+ className="absolute right-4 top-4 rounded-sm opacity-70 hover:opacity-100 transition-opacity">
3886
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
3887
+ </button>
3888
+ </div>
3889
+ </div>
3890
+ )}
3891
+ </>
3892
+ )
3893
+ }
3894
+
3895
+ function DropdownMenuPreview({ size }: { size?: string }) {
3896
+ const [open, setOpen] = useState(false)
3897
+ const sizeClasses: Record<string, string> = {
3898
+ sm: 'min-w-[6rem]',
3899
+ md: 'min-w-[8rem]',
3900
+ lg: 'min-w-[12rem]',
3901
+ }
3902
+ const s = size || 'md'
3903
+ return (
3904
+ <div className="relative">
3905
+ <button onClick={() => setOpen(!open)}
3906
+ className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 h-9 text-sm font-medium hover:bg-muted transition-colors gap-1">
3907
+ Actions {chevronSvg}
3908
+ </button>
3909
+ {open && (
3910
+ <>
3911
+ <div className="fixed inset-0 z-40" onClick={() => setOpen(false)} />
3912
+ <div className={cn('absolute top-full left-0 z-50 mt-1 rounded-md border bg-popover text-popover-foreground p-1 shadow-md animate-in fade-in-0 zoom-in-95', sizeClasses[s] || sizeClasses.md)}>
3913
+ <button onClick={() => setOpen(false)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">
3914
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/></svg>
3915
+ Edit
3916
+ </button>
3917
+ <button onClick={() => setOpen(false)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">
3918
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
3919
+ Duplicate
3920
+ </button>
3921
+ <div className="-mx-1 my-1 h-px bg-border" />
3922
+ <button onClick={() => setOpen(false)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none text-destructive hover:bg-destructive/10 transition-colors">
3923
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
3924
+ Delete
3925
+ </button>
3926
+ </div>
3927
+ </>
3928
+ )}
3929
+ </div>
3930
+ )
3931
+ }
3932
+
3933
+ function ContextMenuPreview() {
3934
+ const [pos, setPos] = useState<{ x: number; y: number } | null>(null)
3935
+ return (
3936
+ <div className="relative">
3937
+ <div
3938
+ onContextMenu={e => { e.preventDefault(); setPos({ x: e.nativeEvent.offsetX, y: e.nativeEvent.offsetY }) }}
3939
+ className="flex items-center justify-center w-48 h-24 rounded-lg border-2 border-dashed border-muted-foreground/25 text-sm text-muted-foreground cursor-context-menu select-none hover:border-muted-foreground/40 transition-colors"
3940
+ >
3941
+ Right-click here
3942
+ </div>
3943
+ {pos && (
3944
+ <>
3945
+ <div className="fixed inset-0 z-40" onClick={() => setPos(null)} onContextMenu={e => { e.preventDefault(); setPos(null) }} />
3946
+ <div style={{ position: 'absolute', top: pos.y, left: pos.x }} className="z-50 min-w-[8rem] rounded-md border bg-popover text-popover-foreground p-1 shadow-md animate-in fade-in-0 zoom-in-95">
3947
+ <button onClick={() => setPos(null)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">View</button>
3948
+ <button onClick={() => setPos(null)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">Edit</button>
3949
+ <div className="-mx-1 my-1 h-px bg-border" />
3950
+ <button onClick={() => setPos(null)} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none text-destructive hover:bg-destructive/10 transition-colors">Delete</button>
3951
+ </div>
3952
+ </>
3953
+ )}
3954
+ </div>
3955
+ )
3956
+ }
3957
+
3958
+ function CommandPreview() {
3959
+ const [query, setQuery] = useState('')
3960
+ const items = ['Dashboard', 'Projects', 'Team', 'Settings', 'Profile']
3961
+ const filtered = items.filter(i => i.toLowerCase().includes(query.toLowerCase()))
3962
+ return (
3963
+ <div className="w-72 rounded-lg border bg-popover text-popover-foreground shadow-md">
3964
+ <div className="flex items-center border-b px-3 h-10">
3965
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="mr-2 shrink-0 opacity-50"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
3966
+ <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Type a command\u2026" className="flex h-full w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground" />
3967
+ </div>
3968
+ <div className="p-1 max-h-48 overflow-y-auto">
3969
+ {filtered.length === 0 ? (
3970
+ <p className="py-4 text-center text-sm text-muted-foreground">No results found.</p>
3971
+ ) : (
3972
+ filtered.map(item => (
3973
+ <button key={item} className="relative flex w-full cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground transition-colors">{item}</button>
3974
+ ))
3975
+ )}
3976
+ </div>
3977
+ </div>
3978
+ )
3979
+ }
3980
+
3981
+ function TooltipPreview() {
3982
+ const [show, setShow] = useState(false)
3983
+ return (
3984
+ <div className="relative inline-block">
3985
+ <button
3986
+ onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}
3987
+ className="inline-flex items-center justify-center rounded-md border border-input bg-background px-4 h-9 text-sm font-medium hover:bg-muted transition-colors"
3988
+ >
3989
+ Hover me
3990
+ </button>
3991
+ {show && (
3992
+ <div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-50 rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground shadow-md animate-in fade-in-0 zoom-in-95">
3993
+ Tooltip content
3994
+ <div className="absolute top-full left-1/2 -translate-x-1/2 -mt-px border-4 border-transparent border-t-primary" />
3995
+ </div>
3996
+ )}
3997
+ </div>
3998
+ )
3999
+ }
4000
+
3851
4001
  function Preview({ component, variant, size }: { component: any; variant?: string; size?: string }) {
3852
4002
  const id = component.id
3853
4003
 
@@ -3958,6 +4108,12 @@ function Preview({ component, variant, size }: { component: any; variant?: strin
3958
4108
  )
3959
4109
  }
3960
4110
 
4111
+ if (id === 'sheet') return <SheetPreview side={variant} />
4112
+ if (id === 'dropdown-menu') return <DropdownMenuPreview size={size} />
4113
+ if (id === 'context-menu') return <ContextMenuPreview />
4114
+ if (id === 'command') return <CommandPreview />
4115
+ if (id === 'tooltip') return <TooltipPreview />
4116
+
3961
4117
  const cls = resolveClasses(component, variant, size)
3962
4118
  if (cls) {
3963
4119
  return <div className={cn(cls)}>{displayName(component)}</div>
@@ -3992,6 +4148,10 @@ export default function ComponentShowcase({ component }: ComponentShowcaseProps)
3992
4148
  if (id === 'separator') return \`<\${n} />\`
3993
4149
  if (id === 'progress') return \`<\${n} value={60} />\`
3994
4150
  if (id === 'avatar') return \`<\${n}>\\n <AvatarImage src="/avatar.png" />\\n <AvatarFallback>AV</AvatarFallback>\\n</\${n}>\`
4151
+ if (id === 'sheet') return \`<Sheet>\\n <SheetTrigger asChild>\\n <Button variant="outline">Open</Button>\\n </SheetTrigger>\\n <SheetContent>\\n <SheetHeader>\\n <SheetTitle>Title</SheetTitle>\\n <SheetDescription>Description</SheetDescription>\\n </SheetHeader>\\n </SheetContent>\\n</Sheet>\`
4152
+ if (id === 'dropdown-menu') return \`<DropdownMenu>\\n <DropdownMenuTrigger asChild>\\n <Button variant="outline">Open</Button>\\n </DropdownMenuTrigger>\\n <DropdownMenuContent>\\n <DropdownMenuItem>Action</DropdownMenuItem>\\n </DropdownMenuContent>\\n</DropdownMenu>\`
4153
+ if (id === 'context-menu') return \`<ContextMenu>\\n <ContextMenuTrigger>Right click</ContextMenuTrigger>\\n <ContextMenuContent>\\n <ContextMenuItem>Action</ContextMenuItem>\\n </ContextMenuContent>\\n</ContextMenu>\`
4154
+ if (id === 'command') return \`<Command>\\n <CommandInput placeholder="Type a command..." />\\n <CommandList>\\n <CommandEmpty>No results.</CommandEmpty>\\n <CommandGroup heading="Suggestions">\\n <CommandItem>Calendar</CommandItem>\\n </CommandGroup>\\n </CommandList>\\n</Command>\`
3995
4155
  return \`<\${n} />\`
3996
4156
  })()
3997
4157
 
@@ -5574,9 +5734,9 @@ ${sections}
5574
5734
  getContainerClass(layout) {
5575
5735
  const layoutClasses = {
5576
5736
  centered: "space-y-6",
5577
- "sidebar-left": "flex min-h-screen",
5578
- "sidebar-right": "flex flex-row-reverse min-h-screen",
5579
- "full-width": "w-full",
5737
+ "sidebar-left": "flex gap-6",
5738
+ "sidebar-right": "flex flex-row-reverse gap-6",
5739
+ "full-width": "space-y-6",
5580
5740
  grid: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
5581
5741
  };
5582
5742
  return layoutClasses[layout] || "space-y-6";
@@ -5787,22 +5947,81 @@ export function AppNav() {
5787
5947
  "/forgot-password",
5788
5948
  "/reset-password"
5789
5949
  ]);
5950
+ const marketingRoutes = /* @__PURE__ */ new Set(["/", "/landing", "/pricing", "/about", "/contact", "/blog", "/features"]);
5951
+ const isSubRoute = (route) => route.replace(/^\//, "").split("/").length > 1;
5790
5952
  const visibleItems = navItems.filter(
5791
- (item) => item.route !== "/" && !authRoutes.has(item.route) && !item.route.includes("[")
5953
+ (item) => !marketingRoutes.has(item.route) && !authRoutes.has(item.route) && !item.route.includes("[") && !isSubRoute(item.route)
5792
5954
  );
5793
- const hasMultipleItems = visibleItems.length > 0;
5794
- const items = visibleItems.map(
5955
+ const grouped = /* @__PURE__ */ new Map();
5956
+ const ungrouped = [];
5957
+ for (const item of visibleItems) {
5958
+ if (item.group) {
5959
+ const list = grouped.get(item.group) || [];
5960
+ list.push(item);
5961
+ grouped.set(item.group, list);
5962
+ } else if (item.children && item.children.length > 0) {
5963
+ grouped.set(item.label, [item]);
5964
+ } else {
5965
+ ungrouped.push(item);
5966
+ }
5967
+ }
5968
+ const hasDropdowns = grouped.size > 0;
5969
+ const hasAuthItems = navItems.some((item) => authRoutes.has(item.route));
5970
+ const authItems = navItems.filter((item) => authRoutes.has(item.route));
5971
+ const signInItem = authItems.find((item) => /sign.?in|login/i.test(item.label));
5972
+ const signUpItem = authItems.find((item) => /sign.?up|register/i.test(item.label));
5973
+ const linkItems = ungrouped.map(
5795
5974
  (item) => `<Link href="${item.route}" className={\`text-sm font-medium px-3 py-2 rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring \${pathname === "${item.route}" ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}>${item.label}</Link>`
5796
5975
  ).join("\n ");
5797
- const navItemsBlock = hasMultipleItems ? `
5798
- ${items}
5976
+ const dropdownBlocks = [];
5977
+ for (const [groupName, items] of grouped) {
5978
+ const parentItem = items.length === 1 && items[0].children ? items[0] : null;
5979
+ const childItems = parentItem ? parentItem.children : items;
5980
+ const triggerLabel = parentItem ? parentItem.label : groupName;
5981
+ const menuItems = childItems.map(
5982
+ (child) => ` <DropdownMenuItem asChild>
5983
+ <Link href="${child.route}" className="w-full">${child.label}</Link>
5984
+ </DropdownMenuItem>`
5985
+ ).join("\n");
5986
+ dropdownBlocks.push(`<DropdownMenu>
5987
+ <DropdownMenuTrigger className="flex items-center gap-1 text-sm font-medium px-3 py-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
5988
+ ${triggerLabel}
5989
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m6 9 6 6 6-6"/></svg>
5990
+ </DropdownMenuTrigger>
5991
+ <DropdownMenuContent align="start">
5992
+ ${menuItems}
5993
+ </DropdownMenuContent>
5994
+ </DropdownMenu>`);
5995
+ }
5996
+ const allNavElements = [linkItems, ...dropdownBlocks].filter(Boolean).join("\n ");
5997
+ const navItemsBlock = allNavElements ? `
5998
+ ${allNavElements}
5799
5999
  ` : "";
6000
+ const authButtonsBlock = hasAuthItems && (signInItem || signUpItem) ? `${signInItem ? `
6001
+ <Link href="${signInItem.route}" className="text-sm font-medium px-3 py-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors">${signInItem.label}</Link>` : ""}${signUpItem ? `
6002
+ <Link href="${signUpItem.route}" className="inline-flex items-center justify-center text-sm font-medium h-9 px-4 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors">${signUpItem.label}</Link>` : ""}` : "";
6003
+ const dropdownImport = hasDropdowns ? `
6004
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'` : "";
5800
6005
  const appName = this.escapeString(this.config.name);
6006
+ const mobileNavItems = [...ungrouped];
6007
+ for (const [, items] of grouped) {
6008
+ const parentItem = items.length === 1 && items[0].children ? items[0] : null;
6009
+ const childItems = parentItem ? parentItem.children : items;
6010
+ mobileNavItems.push(...childItems);
6011
+ }
6012
+ const mobileLinks = mobileNavItems.map(
6013
+ (item) => `<Link href="${item.route}" onClick={() => setMobileOpen(false)} className={\`block text-sm font-medium px-3 py-2 rounded-md transition-colors \${pathname === "${item.route}" ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}>${item.label}</Link>`
6014
+ ).join("\n ");
6015
+ const mobileAuthBlock = hasAuthItems && (signInItem || signUpItem) ? `
6016
+ <div className="border-t pt-3 mt-2 space-y-1">${signInItem ? `
6017
+ <Link href="${signInItem.route}" onClick={() => setMobileOpen(false)} className="block text-sm font-medium px-3 py-2 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors">${signInItem.label}</Link>` : ""}${signUpItem ? `
6018
+ <Link href="${signUpItem.route}" onClick={() => setMobileOpen(false)} className="block text-sm font-medium px-3 py-2 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors text-center">${signUpItem.label}</Link>` : ""}
6019
+ </div>` : "";
5801
6020
  return `'use client'
5802
6021
 
5803
6022
  import Link from 'next/link'
5804
6023
  import { usePathname } from 'next/navigation'
5805
- import { useEffect, useState } from 'react'
6024
+ import { useEffect, useState } from 'react'${dropdownImport}
5806
6025
 
5807
6026
  function ThemeToggle() {
5808
6027
  const [dark, setDark] = useState(false)
@@ -5832,6 +6051,12 @@ function ThemeToggle() {
5832
6051
 
5833
6052
  export function Header() {
5834
6053
  const pathname = usePathname()
6054
+ const [mobileOpen, setMobileOpen] = useState(false)
6055
+
6056
+ useEffect(() => {
6057
+ setMobileOpen(false)
6058
+ }, [pathname])
6059
+
5835
6060
  if (pathname?.startsWith('/design-system')) return null
5836
6061
  return (
5837
6062
  <>
@@ -5841,12 +6066,30 @@ export function Header() {
5841
6066
  <Link href="/" className="flex items-center gap-2 text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors shrink-0">
5842
6067
  ${appName}
5843
6068
  </Link>
5844
- <div className="flex items-center gap-1">${navItemsBlock}</div>
6069
+ <div className="hidden md:flex items-center gap-1">${navItemsBlock}</div>
5845
6070
  </div>
5846
- <div className="flex items-center gap-1">
6071
+ <div className="flex items-center gap-1">${authButtonsBlock}
5847
6072
  <ThemeToggle />
6073
+ <button
6074
+ onClick={() => setMobileOpen(!mobileOpen)}
6075
+ className="flex md:hidden items-center justify-center w-9 h-9 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
6076
+ aria-label="Toggle menu"
6077
+ >
6078
+ {mobileOpen ? (
6079
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6080
+ ) : (
6081
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/></svg>
6082
+ )}
6083
+ </button>
5848
6084
  </div>
5849
6085
  </div>
6086
+ {mobileOpen && (
6087
+ <div className="md:hidden border-t bg-background">
6088
+ <div className="mx-auto max-w-7xl px-4 py-3 space-y-1">
6089
+ ${mobileLinks}${mobileAuthBlock}
6090
+ </div>
6091
+ </div>
6092
+ )}
5850
6093
  </nav>
5851
6094
  <Link
5852
6095
  href="/design-system"
@@ -5865,22 +6108,174 @@ export function Header() {
5865
6108
  */
5866
6109
  generateSharedFooterCode() {
5867
6110
  const appName = this.escapeString(this.config.name);
6111
+ const navItems = this.config.navigation?.items || [];
6112
+ const authRoutes = /* @__PURE__ */ new Set([
6113
+ "/login",
6114
+ "/signin",
6115
+ "/sign-in",
6116
+ "/signup",
6117
+ "/sign-up",
6118
+ "/register",
6119
+ "/forgot-password",
6120
+ "/reset-password"
6121
+ ]);
6122
+ const marketingRoutes = /* @__PURE__ */ new Set(["/", "/landing", "/pricing", "/about", "/contact", "/blog", "/features"]);
6123
+ const isSubRoute = (route) => route.replace(/^\//, "").split("/").length > 1;
6124
+ const appLinks = navItems.filter(
6125
+ (item) => !marketingRoutes.has(item.route) && !authRoutes.has(item.route) && !item.route.includes("[") && !isSubRoute(item.route)
6126
+ ).slice(0, 4);
6127
+ const linkElements = appLinks.map(
6128
+ (item) => ` <Link href="${item.route}" className="text-sm text-muted-foreground hover:text-foreground transition-colors">${item.label}</Link>`
6129
+ ).join("\n");
6130
+ const marketingLinks = navItems.filter((item) => marketingRoutes.has(item.route) && item.route !== "/").slice(0, 3);
6131
+ const marketingLinkElements = marketingLinks.map(
6132
+ (item) => ` <Link href="${item.route}" className="text-sm text-muted-foreground hover:text-foreground transition-colors">${item.label}</Link>`
6133
+ ).join("\n");
6134
+ const hasMarketingLinks = marketingLinks.length > 0;
6135
+ const companyColumn = hasMarketingLinks ? ` <div className="flex flex-col space-y-3">
6136
+ <p className="text-sm font-medium text-foreground">Company</p>
6137
+ ${marketingLinkElements}
6138
+ </div>` : "";
5868
6139
  return `'use client'
5869
6140
 
6141
+ import Link from 'next/link'
5870
6142
  import { usePathname } from 'next/navigation'
5871
6143
 
5872
6144
  export function Footer() {
5873
6145
  const pathname = usePathname()
5874
6146
  if (pathname?.startsWith('/design-system')) return null
5875
6147
  return (
5876
- <footer className="border-t">
5877
- <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 text-sm text-muted-foreground sm:px-6 lg:px-8">
5878
- <p>{'\xA9'} {new Date().getFullYear()} ${appName}</p>
5879
- <p className="hidden sm:block">Built with Coherent Design Method</p>
6148
+ <footer className="border-t bg-muted/30">
6149
+ <div className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
6150
+ <div className="flex flex-col gap-8 py-8 md:flex-row md:justify-between">
6151
+ <div className="space-y-3">
6152
+ <Link href="/" className="text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors">
6153
+ ${appName}
6154
+ </Link>
6155
+ <p className="text-sm text-muted-foreground max-w-xs">
6156
+ Modern project management for teams of all sizes.
6157
+ </p>
6158
+ </div>
6159
+ <div className="flex gap-12">
6160
+ <div className="flex flex-col space-y-3">
6161
+ <p className="text-sm font-medium text-foreground">Product</p>
6162
+ ${linkElements}
6163
+ </div>
6164
+ ${companyColumn}
6165
+ </div>
6166
+ </div>
6167
+ <div className="flex items-center justify-between border-t py-4 text-xs text-muted-foreground">
6168
+ <p>{'\xA9'} {new Date().getFullYear()} ${appName}. All rights reserved.</p>
6169
+ <div className="flex gap-4">
6170
+ <span>Privacy Policy</span>
6171
+ <span>Terms of Service</span>
6172
+ </div>
6173
+ </div>
5880
6174
  </div>
5881
6175
  </footer>
5882
6176
  )
5883
6177
  }
6178
+ `;
6179
+ }
6180
+ /**
6181
+ * Generate shared Sidebar component code for components/shared/sidebar.tsx.
6182
+ * Used when navigation.type is 'sidebar' or 'both'.
6183
+ */
6184
+ generateSharedSidebarCode() {
6185
+ const navItems = this.config.navigation?.items || [];
6186
+ const authRoutes = /* @__PURE__ */ new Set([
6187
+ "/login",
6188
+ "/signin",
6189
+ "/sign-in",
6190
+ "/signup",
6191
+ "/sign-up",
6192
+ "/register",
6193
+ "/forgot-password",
6194
+ "/reset-password"
6195
+ ]);
6196
+ const marketingRoutes = /* @__PURE__ */ new Set(["/", "/landing", "/pricing", "/about", "/contact", "/blog", "/features"]);
6197
+ const isSubRoute = (route) => route.replace(/^\//, "").split("/").length > 1;
6198
+ const visibleItems = navItems.filter(
6199
+ (item) => !marketingRoutes.has(item.route) && !authRoutes.has(item.route) && !item.route.includes("[") && !isSubRoute(item.route)
6200
+ );
6201
+ const grouped = /* @__PURE__ */ new Map();
6202
+ const ungrouped = [];
6203
+ for (const item of visibleItems) {
6204
+ if (item.group) {
6205
+ const list = grouped.get(item.group) || [];
6206
+ list.push(item);
6207
+ grouped.set(item.group, list);
6208
+ } else {
6209
+ ungrouped.push(item);
6210
+ }
6211
+ }
6212
+ const linkItems = ungrouped.map(
6213
+ (item) => ` <Link
6214
+ href="${item.route}"
6215
+ className={\`flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors \${pathname === "${item.route}" ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}
6216
+ >
6217
+ ${item.label}
6218
+ </Link>`
6219
+ ).join("\n");
6220
+ const groupBlocks = [];
6221
+ for (const [groupName, items] of grouped) {
6222
+ const groupLinks = items.map(
6223
+ (item) => ` <Link
6224
+ href="${item.route}"
6225
+ className={\`flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors \${pathname === "${item.route}" ? 'bg-muted text-foreground font-medium' : 'text-muted-foreground hover:text-foreground hover:bg-muted/50'}\`}
6226
+ >
6227
+ ${item.label}
6228
+ </Link>`
6229
+ ).join("\n");
6230
+ groupBlocks.push(` <div className="space-y-1">
6231
+ <p className="px-3 py-1 text-xs font-semibold uppercase tracking-wider text-muted-foreground/70">${groupName}</p>
6232
+ ${groupLinks}
6233
+ </div>`);
6234
+ }
6235
+ const allSections = [linkItems, ...groupBlocks].filter(Boolean).join("\n");
6236
+ const appName = this.escapeString(this.config.name);
6237
+ return `'use client'
6238
+
6239
+ import Link from 'next/link'
6240
+ import { usePathname } from 'next/navigation'
6241
+ import { useState } from 'react'
6242
+
6243
+ export function Sidebar() {
6244
+ const pathname = usePathname()
6245
+ const [collapsed, setCollapsed] = useState(false)
6246
+
6247
+ if (pathname?.startsWith('/design-system')) return null
6248
+
6249
+ return (
6250
+ <aside className={\`shrink-0 border-r bg-muted/30 transition-all duration-200 \${collapsed ? 'w-16' : 'w-64'}\`}>
6251
+ <div className="flex h-14 items-center justify-between border-b px-4">
6252
+ {!collapsed && (
6253
+ <Link href="/" className="text-sm font-semibold text-foreground truncate">
6254
+ ${appName}
6255
+ </Link>
6256
+ )}
6257
+ <button
6258
+ onClick={() => setCollapsed(!collapsed)}
6259
+ className="flex items-center justify-center w-8 h-8 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
6260
+ aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
6261
+ >
6262
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
6263
+ {collapsed ? (
6264
+ <><path d="m9 18 6-6-6-6"/></>
6265
+ ) : (
6266
+ <><path d="m15 18-6-6 6-6"/></>
6267
+ )}
6268
+ </svg>
6269
+ </button>
6270
+ </div>
6271
+ {!collapsed && (
6272
+ <nav className="flex flex-col gap-1 p-3">
6273
+ ${allSections}
6274
+ </nav>
6275
+ )}
6276
+ </aside>
6277
+ )
6278
+ }
5884
6279
  `;
5885
6280
  }
5886
6281
  /**
@@ -6715,15 +7110,18 @@ export function cn(...inputs: ClassValue[]) {
6715
7110
  const code = await this.pageGenerator.generateLayout(layout, appType, { skipNav: true });
6716
7111
  await this.writeFile("app/layout.tsx", code);
6717
7112
  if (this.config.navigation?.enabled && appType === "multi-page") {
6718
- const headerCode = this.pageGenerator.generateSharedHeaderCode();
6719
- await generateSharedComponent(this.projectRoot, {
6720
- name: "Header",
6721
- type: "layout",
6722
- code: headerCode,
6723
- description: "Main site header with navigation and theme toggle",
6724
- usedIn: ["app/layout.tsx"]
6725
- });
6726
- const footerCode = this.pageGenerator.generateSharedFooterCode();
7113
+ const navType = this.config.navigation.type || "header";
7114
+ if (navType === "header" || navType === "both") {
7115
+ const headerCode = this.generateInitialHeaderCode();
7116
+ await generateSharedComponent(this.projectRoot, {
7117
+ name: "Header",
7118
+ type: "layout",
7119
+ code: headerCode,
7120
+ description: "Main site header with navigation and theme toggle",
7121
+ usedIn: ["app/layout.tsx"]
7122
+ });
7123
+ }
7124
+ const footerCode = this.generateInitialFooterCode();
6727
7125
  await generateSharedComponent(this.projectRoot, {
6728
7126
  name: "Footer",
6729
7127
  type: "layout",
@@ -6731,6 +7129,16 @@ export function cn(...inputs: ClassValue[]) {
6731
7129
  description: "Site footer",
6732
7130
  usedIn: ["app/layout.tsx"]
6733
7131
  });
7132
+ if (navType === "sidebar" || navType === "both") {
7133
+ const sidebarCode = this.pageGenerator.generateSharedSidebarCode();
7134
+ await generateSharedComponent(this.projectRoot, {
7135
+ name: "Sidebar",
7136
+ type: "layout",
7137
+ code: sidebarCode,
7138
+ description: "Vertical sidebar navigation with collapsible sections",
7139
+ usedIn: ["app/(app)/layout.tsx"]
7140
+ });
7141
+ }
6734
7142
  await integrateSharedLayoutIntoRootLayout(this.projectRoot);
6735
7143
  }
6736
7144
  }
@@ -7205,6 +7613,134 @@ Recommendations are added here when you use \`coherent chat\` and the AI suggest
7205
7613
  this.pageGenerator.updateConfig(newConfig);
7206
7614
  this.tailwindGenerator.updateConfig(newConfig);
7207
7615
  }
7616
+ /**
7617
+ * Initial header for coherent init — Coherent Design Method branding.
7618
+ * Replaced by app-branded header on first `coherent chat` via regenerateLayout().
7619
+ */
7620
+ generateInitialHeaderCode() {
7621
+ return `'use client'
7622
+
7623
+ import Link from 'next/link'
7624
+ import { usePathname } from 'next/navigation'
7625
+ import { useEffect, useState } from 'react'
7626
+
7627
+ function ThemeToggle() {
7628
+ const [dark, setDark] = useState(false)
7629
+ useEffect(() => {
7630
+ setDark(document.documentElement.classList.contains('dark'))
7631
+ }, [])
7632
+ const toggle = () => {
7633
+ const next = !dark
7634
+ setDark(next)
7635
+ document.documentElement.classList.toggle('dark', next)
7636
+ }
7637
+ return (
7638
+ <button
7639
+ onClick={toggle}
7640
+ className="flex items-center justify-center w-9 h-9 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
7641
+ title={dark ? 'Switch to light theme' : 'Switch to dark theme'}
7642
+ aria-label="Toggle theme"
7643
+ >
7644
+ {dark ? (
7645
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
7646
+ ) : (
7647
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
7648
+ )}
7649
+ </button>
7650
+ )
7651
+ }
7652
+
7653
+ export function Header() {
7654
+ const pathname = usePathname()
7655
+ if (pathname?.startsWith('/design-system')) return null
7656
+ return (
7657
+ <>
7658
+ <nav className="sticky top-0 z-50 shrink-0 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
7659
+ <div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
7660
+ <div className="flex items-center gap-3">
7661
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="shrink-0 text-primary">
7662
+ <path d="M10 4C10.5523 4 11 4.44772 11 5V13H19C19.5523 13 20 13.4477 20 14V20.4287C19.9999 22.401 18.401 23.9999 16.4287 24H3.57129C1.59895 23.9999 7.5245e-05 22.401 0 20.4287V7.57129C7.53742e-05 5.59895 1.59895 4.00008 3.57129 4H10ZM2 20.4287C2.00008 21.2965 2.70352 21.9999 3.57129 22H9V15H2V20.4287ZM11 22H16.4287C17.2965 21.9999 17.9999 21.2965 18 20.4287V15H11V22ZM3.57129 6C2.70352 6.00008 2.00008 6.70352 2 7.57129V13H9V6H3.57129ZM20.5 0C22.433 0 24 1.567 24 3.5V9.90039C23.9998 10.5076 23.5076 10.9998 22.9004 11H14.0996C13.4924 10.9998 13.0002 10.5076 13 9.90039V1.09961C13.0002 0.492409 13.4924 0.000211011 14.0996 0H20.5ZM15 9H22V3.5C22 2.67157 21.3284 2 20.5 2H15V9Z" fill="currentColor"/>
7663
+ </svg>
7664
+ <Link href="/" className="text-sm font-semibold text-foreground hover:text-foreground/90 transition-colors">
7665
+ Coherent Design Method
7666
+ </Link>
7667
+ </div>
7668
+ <div className="flex items-center gap-1">
7669
+ <ThemeToggle />
7670
+ </div>
7671
+ </div>
7672
+ </nav>
7673
+ <Link
7674
+ href="/design-system"
7675
+ className="fixed bottom-4 right-4 z-50 flex items-center gap-2 rounded-full border border-white/20 bg-black/60 backdrop-blur-md text-white px-4 py-2 text-xs shadow-sm hover:bg-black/80 transition-all"
7676
+ title="Design System"
7677
+ >
7678
+ Design System
7679
+ </Link>
7680
+ </>
7681
+ )
7682
+ }
7683
+ `;
7684
+ }
7685
+ /**
7686
+ * Initial footer for coherent init — Coherent Design Method branding.
7687
+ * Replaced by app-branded footer on first `coherent chat` via regenerateLayout().
7688
+ */
7689
+ generateInitialFooterCode() {
7690
+ return `'use client'
7691
+
7692
+ import Link from 'next/link'
7693
+ import { usePathname } from 'next/navigation'
7694
+
7695
+ export function Footer() {
7696
+ const pathname = usePathname()
7697
+ if (pathname?.startsWith('/design-system')) return null
7698
+ return (
7699
+ <footer className="border-t bg-background">
7700
+ <div className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8">
7701
+ <div className="grid grid-cols-2 gap-8 py-10 md:grid-cols-4">
7702
+ <div className="col-span-2 md:col-span-1">
7703
+ <div className="flex items-center gap-2 mb-2">
7704
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="shrink-0 text-primary">
7705
+ <path d="M10 4C10.5523 4 11 4.44772 11 5V13H19C19.5523 13 20 13.4477 20 14V20.4287C19.9999 22.401 18.401 23.9999 16.4287 24H3.57129C1.59895 23.9999 7.5245e-05 22.401 0 20.4287V7.57129C7.53742e-05 5.59895 1.59895 4.00008 3.57129 4H10ZM2 20.4287C2.00008 21.2965 2.70352 21.9999 3.57129 22H9V15H2V20.4287ZM11 22H16.4287C17.2965 21.9999 17.9999 21.2965 18 20.4287V15H11V22ZM3.57129 6C2.70352 6.00008 2.00008 6.70352 2 7.57129V13H9V6H3.57129ZM20.5 0C22.433 0 24 1.567 24 3.5V9.90039C23.9998 10.5076 23.5076 10.9998 22.9004 11H14.0996C13.4924 10.9998 13.0002 10.5076 13 9.90039V1.09961C13.0002 0.492409 13.4924 0.000211011 14.0996 0H20.5ZM15 9H22V3.5C22 2.67157 21.3284 2 20.5 2H15V9Z" fill="currentColor"/>
7706
+ </svg>
7707
+ <span className="text-sm font-semibold">Coherent Design Method</span>
7708
+ </div>
7709
+ <p className="mt-1 text-sm text-muted-foreground">
7710
+ Design once. Stay consistent everywhere.
7711
+ </p>
7712
+ </div>
7713
+ <div>
7714
+ <h4 className="mb-3 text-sm font-semibold">Product</h4>
7715
+ <ul className="space-y-2">
7716
+ <li><Link href="/design-system" className="text-sm text-muted-foreground hover:text-foreground transition-colors">Design System</Link></li>
7717
+ <li><a href="https://getcoherent.design" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">Website</a></li>
7718
+ <li><a href="https://github.com/skovtun/coherent-design-method" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">GitHub</a></li>
7719
+ </ul>
7720
+ </div>
7721
+ <div>
7722
+ <h4 className="mb-3 text-sm font-semibold">Legal</h4>
7723
+ <ul className="space-y-2">
7724
+ <li><span className="text-sm text-muted-foreground cursor-pointer hover:text-foreground transition-colors">Terms of Use</span></li>
7725
+ <li><span className="text-sm text-muted-foreground cursor-pointer hover:text-foreground transition-colors">Privacy Policy</span></li>
7726
+ </ul>
7727
+ </div>
7728
+ <div>
7729
+ <h4 className="mb-3 text-sm font-semibold">Author</h4>
7730
+ <ul className="space-y-2">
7731
+ <li><a href="https://www.linkedin.com/in/sergeikovtun/" target="_blank" rel="noopener noreferrer" className="text-sm text-muted-foreground hover:text-foreground transition-colors">Sergei Kovtun</a></li>
7732
+ </ul>
7733
+ </div>
7734
+ </div>
7735
+ <div className="border-t py-6 text-center text-xs text-muted-foreground">
7736
+ {'\\u00A9'} {new Date().getFullYear()} Coherent Design Method. All rights reserved.
7737
+ </div>
7738
+ </div>
7739
+ </footer>
7740
+ )
7741
+ }
7742
+ `;
7743
+ }
7208
7744
  };
7209
7745
 
7210
7746
  // src/generators/templates/pages/_shared.ts
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.0",
6
+ "version": "0.5.0",
7
7
  "description": "Core design system engine for Coherent",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",
@@ -32,12 +32,6 @@
32
32
  ],
33
33
  "author": "Coherent Design Method",
34
34
  "license": "MIT",
35
- "scripts": {
36
- "dev": "tsup --watch",
37
- "build": "tsup",
38
- "typecheck": "tsc --noEmit",
39
- "test": "vitest"
40
- },
41
35
  "dependencies": {
42
36
  "handlebars": "^4.7.8",
43
37
  "zod": "^3.22.4"
@@ -47,5 +41,11 @@
47
41
  "tsup": "^8.0.1",
48
42
  "typescript": "^5.3.3",
49
43
  "vitest": "^1.2.1"
44
+ },
45
+ "scripts": {
46
+ "dev": "tsup --watch",
47
+ "build": "tsup",
48
+ "typecheck": "tsc --noEmit",
49
+ "test": "vitest"
50
50
  }
51
- }
51
+ }