@getcoherent/cli 0.3.12 → 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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.js +170 -123
  3. package/package.json +10 -10
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.js CHANGED
@@ -698,10 +698,7 @@ function generateWelcomeComponent(_markdown) {
698
698
  import { useState } from 'react'
699
699
  import {
700
700
  ArrowRight,
701
- Blocks,
702
701
  ClipboardCopy,
703
- Eye,
704
- Globe,
705
702
  LayoutDashboard,
706
703
  LogIn,
707
704
  Monitor,
@@ -709,7 +706,6 @@ import {
709
706
  Rocket,
710
707
  Settings,
711
708
  ShoppingBag,
712
- Sparkles,
713
709
  } from 'lucide-react'
714
710
 
715
711
  export default function HomePage() {
@@ -1020,47 +1016,7 @@ export default function HomePage() {
1020
1016
  </div>
1021
1017
  </section>
1022
1018
 
1023
- {/* Footer links (shared Footer component provides the main footer) */}
1024
- <div className="border-t pt-8 pb-8 mt-auto">
1025
- <div className="flex flex-col items-center gap-5 px-4">
1026
- <div className="flex items-center gap-3">
1027
- <div className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground shrink-0">
1028
- <Blocks className="size-4" />
1029
- </div>
1030
- <span className="text-sm font-semibold">Coherent Design Method</span>
1031
- </div>
1032
- <div className="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">
1033
- <a
1034
- href="https://getcoherent.design"
1035
- target="_blank"
1036
- rel="noopener noreferrer"
1037
- className="text-xs text-muted-foreground hover:text-foreground transition-colors"
1038
- >
1039
- getcoherent.design
1040
- </a>
1041
- <a href="https://github.com/skovtun/coherent-design-method" target="_blank" rel="noopener noreferrer" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
1042
- GitHub
1043
- </a>
1044
- <a href="#" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
1045
- Terms of Use
1046
- </a>
1047
- <a href="#" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
1048
- Privacy Policy
1049
- </a>
1050
- </div>
1051
- <p className="text-xs text-muted-foreground">
1052
- \xA9 {new Date().getFullYear()}{' '}
1053
- <a
1054
- href="https://www.linkedin.com/in/sergeikovtun/"
1055
- target="_blank"
1056
- rel="noopener noreferrer"
1057
- className="underline underline-offset-2 hover:text-foreground transition-colors"
1058
- >
1059
- Sergei Kovtun
1060
- </a>
1061
- </p>
1062
- </div>
1063
- </div>
1019
+
1064
1020
  </div>
1065
1021
  )
1066
1022
  }
@@ -2370,6 +2326,7 @@ export default config
2370
2326
  }
2371
2327
  await scaffolder.generateRootLayout();
2372
2328
  await configureNextImages(projectPath);
2329
+ await createAppRouteGroupLayout(projectPath);
2373
2330
  const welcomeMarkdown = getWelcomeMarkdown();
2374
2331
  const homePageContent = generateWelcomeComponent(welcomeMarkdown);
2375
2332
  await writeFile(join5(projectPath, "app", "page.tsx"), homePageContent);
@@ -2397,6 +2354,7 @@ export default config
2397
2354
  console.log(chalk4.yellow(` Run manually: npm install ${COHERENT_REQUIRED_PACKAGES.join(" ")}`));
2398
2355
  }
2399
2356
  await ensureRegistryComponents(config2, projectPath);
2357
+ await createAppRouteGroupLayout(projectPath);
2400
2358
  const designSystemSpinner = ora("Creating design system pages...").start();
2401
2359
  await scaffolder.generateDesignSystemPages();
2402
2360
  designSystemSpinner.succeed("Design system pages created");
@@ -2448,6 +2406,23 @@ export default nextConfig;
2448
2406
  `;
2449
2407
  await writeFile(configPath, content);
2450
2408
  }
2409
+ async function createAppRouteGroupLayout(projectPath) {
2410
+ const dir = join5(projectPath, "app", "(app)");
2411
+ mkdirSync3(dir, { recursive: true });
2412
+ const layoutCode = `export default function AppLayout({
2413
+ children,
2414
+ }: {
2415
+ children: React.ReactNode
2416
+ }) {
2417
+ return (
2418
+ <main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
2419
+ {children}
2420
+ </main>
2421
+ )
2422
+ }
2423
+ `;
2424
+ await writeFile(join5(dir, "layout.tsx"), layoutCode);
2425
+ }
2451
2426
 
2452
2427
  // src/commands/chat.ts
2453
2428
  import chalk13 from "chalk";
@@ -4511,21 +4486,23 @@ LAYOUT CONTRACT (CRITICAL \u2014 prevents duplicate navigation and footer):
4511
4486
  - The app has a root layout (app/layout.tsx) that renders a shared Header and Footer.
4512
4487
  - Pages are rendered INSIDE this layout, between the Header and Footer.
4513
4488
  - NEVER include <header>, <nav>, or <footer> elements in pageCode. Also do NOT add a footer-like section at the bottom (no "\xA9 2024", no site links, no logo + nav links at the bottom).
4514
- - Start page content with <main> or a wrapper <div>. The first visible element should be the page title or hero section.
4515
4489
  - If the page needs sub-navigation (tabs, breadcrumbs, sidebar nav), use elements like <div role="tablist"> or <aside> \u2014 NOT <header>, <nav>, or <footer>.
4516
4490
  - Do NOT add any navigation bars, logo headers, site-wide menus, or site footers to pages. The layout provides all of these.
4517
4491
 
4518
- WIDTH CONSISTENCY (CRITICAL \u2014 prevents visual mismatch between header and content):
4519
- - The shared Header and Footer use "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8" for inner content.
4520
- - ALL page content MUST use the same width constraint: wrap the outermost element in <main className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">.
4521
- - Landing/marketing pages: each <section> is full-width for backgrounds, but inner content uses "mx-auto max-w-6xl" or similar.
4522
- - NEVER use max-w-4xl or smaller for the page wrapper \u2014 it makes the page look narrower than the header.
4523
- - NEVER add inner centering wrappers like <div className="max-w-4xl mx-auto"> inside the page. All content (title, cards, forms) must flow within the same max-w-7xl container so everything aligns to the same left edge.
4524
- - NEVER have page content at full width while header/footer are constrained \u2014 this looks broken.
4492
+ PAGE WRAPPER (CRITICAL \u2014 the layout provides width/padding automatically):
4493
+ - App pages are rendered inside a route group layout that ALREADY provides: <main className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
4494
+ - Your outermost element MUST be exactly: <div className="space-y-6">
4495
+ - FORBIDDEN on the outermost element: <main>, max-w-*, mx-auto, px-*, py-*, p-*, flex-1, min-h-*
4496
+ - FORBIDDEN anywhere: <div className="max-w-4xl mx-auto">, <div className="max-w-2xl mx-auto">, or any inner centering wrapper
4497
+ - The first child inside <div className="space-y-6"> should be the page header (h1 + description)
4498
+ - ALL app pages must follow this exact same structure so content aligns consistently across pages
4499
+ - Landing/marketing pages are an exception: they render outside the app layout and should use full-width <section> elements with inner "mx-auto max-w-6xl" for content.
4525
4500
 
4526
4501
  PAGE CONTENT (CRITICAL \u2014 prevents empty or duplicate pages):
4527
4502
  - Every page MUST have substantial content. NEVER generate a page with only metadata and an empty <main> element.
4528
4503
  - NEVER create an inline preview/demo of another page (e.g., embedding a "dashboard view" inside the landing page with a toggle). Each page should be its own route.
4504
+ - NEVER create a single-page app (SPA) that renders multiple views via useState. Each view must be a separate Next.js page with its own route.
4505
+ - The home page (route "/") should be a simple redirect using next/navigation redirect('/dashboard') \u2014 OR a standalone landing page. NEVER a multi-view SPA.
4529
4506
  - Landing pages should link to app pages via <Link href="/dashboard">, NOT via useState toggles that render inline content.
4530
4507
 
4531
4508
  pageCode rules (shadcn/ui blocks quality):
@@ -4533,7 +4510,7 @@ pageCode rules (shadcn/ui blocks quality):
4533
4510
  - Follow ALL design constraints above: text-sm base, semantic colors only, restricted spacing, weight-based hierarchy.
4534
4511
  - Stat card pattern: Card > CardHeader(flex flex-row items-center justify-between space-y-0 pb-2) > CardTitle(text-sm font-medium) + Icon(size-4 text-muted-foreground) ; CardContent > metric(text-2xl font-bold) + change(text-xs text-muted-foreground).
4535
4512
  - Login/form pattern: outer div(flex min-h-svh flex-col items-center justify-center p-6 md:p-10) > inner div(w-full max-w-sm) > Card with form.
4536
- - Dashboard pattern: main(mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6 flex flex-1 flex-col gap-4) > page header(h1 text-2xl font-bold tracking-tight + p text-sm text-muted-foreground) > stats grid(grid gap-4 md:grid-cols-2 lg:grid-cols-4) > content cards.
4513
+ - Dashboard pattern: div(space-y-6) > page header(h1 text-2xl font-bold tracking-tight + p text-sm text-muted-foreground) > stats grid(grid gap-4 md:grid-cols-2 lg:grid-cols-4) > content cards. No <main> wrapper \u2014 the layout provides it.
4537
4514
  - No placeholders: real contextual copy only. Use the EXACT text, language, and content from the user's request.
4538
4515
  - IMAGES: For avatar/profile photos, use https://i.pravatar.cc/150?u=<unique-seed> (e.g. ?u=sarah.johnson). For hero/product images, use https://picsum.photos/800/400?random=N. Use standard <img> tags with className, NOT Next.js <Image>. Always provide alt text.
4539
4516
  - BUTTON + LINK: The Button component supports asChild prop. To make a button that navigates, use <Button asChild><Link href="/path"><Plus className="size-4" /> Label</Link></Button>. Never nest <button> inside <Link> or vice versa without asChild.
@@ -5049,6 +5026,11 @@ import { resolve as resolve5 } from "path";
5049
5026
  import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
5050
5027
  import { DesignSystemManager as DesignSystemManager3, loadManifest as loadManifest4 } from "@getcoherent/core";
5051
5028
  import chalk8 from "chalk";
5029
+ var MARKETING_ROUTES = /* @__PURE__ */ new Set(["", "landing", "pricing", "about", "contact", "blog", "features"]);
5030
+ function isMarketingRoute(route) {
5031
+ const slug = route.replace(/^\//, "").split("/")[0] || "";
5032
+ return MARKETING_ROUTES.has(slug);
5033
+ }
5052
5034
  function routeToFsPath(projectRoot, route, isAuth) {
5053
5035
  const slug = route.replace(/^\//, "");
5054
5036
  if (isAuth) {
@@ -5057,7 +5039,10 @@ function routeToFsPath(projectRoot, route, isAuth) {
5057
5039
  if (!slug) {
5058
5040
  return resolve5(projectRoot, "app", "page.tsx");
5059
5041
  }
5060
- return resolve5(projectRoot, "app", slug, "page.tsx");
5042
+ if (isMarketingRoute(route)) {
5043
+ return resolve5(projectRoot, "app", slug, "page.tsx");
5044
+ }
5045
+ return resolve5(projectRoot, "app", "(app)", slug, "page.tsx");
5061
5046
  }
5062
5047
  function routeToRelPath(route, isAuth) {
5063
5048
  const slug = route.replace(/^\//, "");
@@ -5067,7 +5052,10 @@ function routeToRelPath(route, isAuth) {
5067
5052
  if (!slug) {
5068
5053
  return "app/page.tsx";
5069
5054
  }
5070
- return `app/${slug}/page.tsx`;
5055
+ if (isMarketingRoute(route)) {
5056
+ return `app/${slug}/page.tsx`;
5057
+ }
5058
+ return `app/(app)/${slug}/page.tsx`;
5071
5059
  }
5072
5060
  function deduplicatePages(pages) {
5073
5061
  const normalize = (route) => route.replace(/\/$/, "").replace(/s$/, "").replace(/ue$/, "");
@@ -6778,15 +6766,18 @@ async function regenerateLayout(config2, projectRoot) {
6778
6766
  const layoutPath = resolve6(projectRoot, "app", "layout.tsx");
6779
6767
  await writeFile(layoutPath, code);
6780
6768
  if (config2.navigation?.enabled && appType === "multi-page") {
6781
- const headerCode = generator.generateSharedHeaderCode();
6782
- await generateSharedComponent2(projectRoot, {
6783
- name: "Header",
6784
- type: "layout",
6785
- code: headerCode,
6786
- description: "Main site header with navigation and theme toggle",
6787
- usedIn: ["app/layout.tsx"],
6788
- overwrite: true
6789
- });
6769
+ const navType = config2.navigation.type || "header";
6770
+ if (navType === "header" || navType === "both") {
6771
+ const headerCode = generator.generateSharedHeaderCode();
6772
+ await generateSharedComponent2(projectRoot, {
6773
+ name: "Header",
6774
+ type: "layout",
6775
+ code: headerCode,
6776
+ description: "Main site header with navigation and theme toggle",
6777
+ usedIn: ["app/layout.tsx"],
6778
+ overwrite: true
6779
+ });
6780
+ }
6790
6781
  if (!hasSharedFooter) {
6791
6782
  const footerCode = generator.generateSharedFooterCode();
6792
6783
  await generateSharedComponent2(projectRoot, {
@@ -6798,16 +6789,70 @@ async function regenerateLayout(config2, projectRoot) {
6798
6789
  overwrite: true
6799
6790
  });
6800
6791
  }
6792
+ if (navType === "sidebar" || navType === "both") {
6793
+ const sidebarCode = generator.generateSharedSidebarCode();
6794
+ await generateSharedComponent2(projectRoot, {
6795
+ name: "Sidebar",
6796
+ type: "layout",
6797
+ code: sidebarCode,
6798
+ description: "Vertical sidebar navigation with collapsible sections",
6799
+ usedIn: ["app/(app)/layout.tsx"],
6800
+ overwrite: true
6801
+ });
6802
+ }
6801
6803
  }
6802
6804
  try {
6803
6805
  await integrateSharedLayoutIntoRootLayout2(projectRoot);
6804
6806
  await ensureAuthRouteGroup(projectRoot);
6807
+ await ensureAppRouteGroupLayout(projectRoot, config2.navigation?.type);
6805
6808
  } catch (err) {
6806
6809
  if (process.env.COHERENT_DEBUG === "1") {
6807
6810
  console.log(chalk9.dim("Layout integration warning:", err));
6808
6811
  }
6809
6812
  }
6810
6813
  }
6814
+ async function ensureAppRouteGroupLayout(projectRoot, navType) {
6815
+ const layoutPath = resolve6(projectRoot, "app", "(app)", "layout.tsx");
6816
+ if (existsSync14(layoutPath)) return;
6817
+ const { mkdir: mkdirAsync } = await import("fs/promises");
6818
+ await mkdirAsync(resolve6(projectRoot, "app", "(app)"), { recursive: true });
6819
+ const code = buildAppLayoutCode(navType);
6820
+ await writeFile(layoutPath, code);
6821
+ }
6822
+ function buildAppLayoutCode(navType) {
6823
+ const hasSidebar = navType === "sidebar" || navType === "both";
6824
+ if (hasSidebar) {
6825
+ return `import { Sidebar } from '@/components/shared/sidebar'
6826
+
6827
+ export default function AppLayout({
6828
+ children,
6829
+ }: {
6830
+ children: React.ReactNode
6831
+ }) {
6832
+ return (
6833
+ <div className="flex min-h-[calc(100vh-3.5rem)]">
6834
+ <Sidebar />
6835
+ <main className="flex-1 px-4 sm:px-6 lg:px-8 py-6">
6836
+ {children}
6837
+ </main>
6838
+ </div>
6839
+ )
6840
+ }
6841
+ `;
6842
+ }
6843
+ return `export default function AppLayout({
6844
+ children,
6845
+ }: {
6846
+ children: React.ReactNode
6847
+ }) {
6848
+ return (
6849
+ <main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
6850
+ {children}
6851
+ </main>
6852
+ )
6853
+ }
6854
+ `;
6855
+ }
6811
6856
  async function regenerateFiles(modified, config2, projectRoot) {
6812
6857
  const componentIds = /* @__PURE__ */ new Set();
6813
6858
  const pageIds = /* @__PURE__ */ new Set();
@@ -7141,61 +7186,41 @@ function stripInlineLayoutElements(code) {
7141
7186
  }
7142
7187
  return { code: result, stripped };
7143
7188
  }
7144
- function enforceWidthWrapper(code, route) {
7145
- if (route === "/" || route === "/home") return { code, fixed: false };
7146
- if (/<section[\s>]/i.test(code) && (/<section[\s>]/gi.exec(code) || []).length >= 2) {
7147
- return { code, fixed: false };
7189
+ var STANDARD_PAGE_WRAPPER = "space-y-6";
7190
+ var HOME_REDIRECT_CODE = `import { redirect } from 'next/navigation'
7191
+
7192
+ export default function Home() {
7193
+ redirect('/dashboard')
7194
+ }
7195
+ `;
7196
+ function detectAndFixSpaHomePage(code, route) {
7197
+ if (route !== "/" && route !== "") return { code, fixed: false };
7198
+ const hasMultipleRenders = (code.match(/const render\w+\s*=\s*\(\)/g) || []).length >= 2;
7199
+ const hasPageToggle = /useState\s*\(\s*['"](?:dashboard|home|page)/i.test(code);
7200
+ const isMassive = code.split("\n").length > 200;
7201
+ if (hasMultipleRenders && hasPageToggle || isMassive && hasPageToggle) {
7202
+ return { code: HOME_REDIRECT_CODE, fixed: true };
7148
7203
  }
7204
+ return { code, fixed: false };
7205
+ }
7206
+ function normalizePageWrapper(code) {
7149
7207
  let result = code;
7150
- let didFix = false;
7151
- const mainMatch = result.match(/<main\s+className="([^"]*)"/);
7152
- if (mainMatch) {
7153
- const cls = mainMatch[1];
7154
- const needsMaxW = !cls.includes("max-w-7xl");
7155
- const needsMxAuto = !cls.includes("mx-auto");
7156
- const needsPadding = !cls.includes("px-");
7157
- if (needsMaxW || needsMxAuto || needsPadding) {
7158
- let fixed = cls;
7159
- fixed = fixed.replace(/\bmax-w-\S+/g, "");
7160
- fixed = `${needsMxAuto ? "mx-auto " : ""}max-w-7xl ${needsPadding ? "px-4 sm:px-6 lg:px-8 " : ""}${fixed}`.replace(/\s+/g, " ").trim();
7161
- result = result.replace(mainMatch[0], `<main className="${fixed}"`);
7162
- didFix = true;
7208
+ let fixed = false;
7209
+ if (/<main\s+className="[^"]*">/.test(result)) {
7210
+ result = result.replace(/<main\s+className="[^"]*">/g, `<div className="${STANDARD_PAGE_WRAPPER}">`);
7211
+ result = result.replace(/<\/main>/g, "</div>");
7212
+ fixed = true;
7213
+ }
7214
+ const outerDivRe = /(return\s*\(\s*\n?\s*)<div\s+className="([^"]*)">/;
7215
+ const match = result.match(outerDivRe);
7216
+ if (match) {
7217
+ const cls = match[2];
7218
+ if (cls !== STANDARD_PAGE_WRAPPER) {
7219
+ result = result.replace(match[0], `${match[1]}<div className="${STANDARD_PAGE_WRAPPER}">`);
7220
+ fixed = true;
7163
7221
  }
7164
- } else {
7165
- const returnMatch = result.match(/return\s*\(\s*\n?\s*<(div|main)\s+className="([^"]*)"/);
7166
- if (returnMatch) {
7167
- const [fullMatch, tag, cls] = returnMatch;
7168
- const needsMaxW = !cls.includes("max-w-7xl");
7169
- const needsMxAuto = !cls.includes("mx-auto");
7170
- const needsPadding = !cls.includes("px-");
7171
- if (needsMaxW || needsMxAuto || needsPadding) {
7172
- let fixed = cls;
7173
- fixed = fixed.replace(/\bmax-w-\S+/g, "");
7174
- fixed = `${needsMxAuto ? "mx-auto " : ""}max-w-7xl ${needsPadding ? "px-4 sm:px-6 lg:px-8 " : ""}${fixed}`.replace(/\s+/g, " ").trim();
7175
- result = result.replace(fullMatch, `return (
7176
- <${tag} className="${fixed}"`);
7177
- didFix = true;
7178
- }
7179
- }
7180
- }
7181
- const isAuthPage = /\/(login|signin|sign-in|signup|sign-up|register|auth)\b/i.test(route);
7182
- if (!isAuthPage) {
7183
- const before = result;
7184
- result = result.replace(
7185
- /(<div\s+className="[^"]*)\bmax-w-(xs|sm|md|lg|xl|2xl|3xl|4xl|5xl|6xl)\b/g,
7186
- "$1"
7187
- );
7188
- result = result.replace(
7189
- /(<div\s+className="[^"]*)\bmx-auto\b/g,
7190
- "$1"
7191
- );
7192
- result = result.replace(
7193
- /className="([^"]*)"/g,
7194
- (_, classes) => `className="${classes.replace(/\s{2,}/g, " ").trim()}"`
7195
- );
7196
- if (result !== before) didFix = true;
7197
7222
  }
7198
- return { code: result, fixed: didFix };
7223
+ return { code: result, fixed };
7199
7224
  }
7200
7225
  async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider, originalMessage) {
7201
7226
  switch (request.type) {
@@ -7528,7 +7553,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7528
7553
  console.log(chalk11.yellow(`
7529
7554
  \u26A0\uFE0F Page "${page.name || page.id}" has no generated code \u2014 it will appear empty.`));
7530
7555
  console.log(chalk11.dim(" This usually means the AI did not produce pageCode for this page."));
7531
- console.log(chalk11.dim(' Try running: coherent chat "regenerate the ' + (page.name || page.id) + ' page with full content"'));
7556
+ console.log(
7557
+ chalk11.dim(
7558
+ ' Try running: coherent chat "regenerate the ' + (page.name || page.id) + ' page with full content"'
7559
+ )
7560
+ );
7532
7561
  }
7533
7562
  const pageForConfig = {
7534
7563
  ...page,
@@ -7569,13 +7598,22 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7569
7598
  let codeToWrite = fixedCode;
7570
7599
  const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
7571
7600
  codeToWrite = autoFixed;
7601
+ const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
7602
+ if (spaWasFixed) {
7603
+ codeToWrite = spaFixed;
7604
+ autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
7605
+ }
7572
7606
  const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
7573
7607
  codeToWrite = layoutStripped;
7574
- const { code: widthFixed, fixed: widthWasFixed } = enforceWidthWrapper(codeToWrite, route);
7575
- codeToWrite = widthFixed;
7608
+ if (!isMarketingRoute(route)) {
7609
+ const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
7610
+ if (wrapperFixed) {
7611
+ codeToWrite = normalized;
7612
+ autoFixes.push("normalized page wrapper to standard spacing");
7613
+ }
7614
+ }
7576
7615
  const allFixes = [...postFixes, ...autoFixes];
7577
7616
  if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
7578
- if (widthWasFixed) allFixes.push("enforced max-w-7xl width consistency");
7579
7617
  if (allFixes.length > 0) {
7580
7618
  console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
7581
7619
  allFixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
@@ -7739,13 +7777,22 @@ ${pagesCtx}`
7739
7777
  let codeToWrite = fixedCode;
7740
7778
  const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
7741
7779
  codeToWrite = autoFixed;
7780
+ const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
7781
+ if (spaWasFixed) {
7782
+ codeToWrite = spaFixed;
7783
+ autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
7784
+ }
7742
7785
  const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
7743
7786
  codeToWrite = layoutStripped;
7744
- const { code: widthFixed2, fixed: widthWasFixed2 } = enforceWidthWrapper(codeToWrite, route);
7745
- codeToWrite = widthFixed2;
7787
+ if (!isMarketingRoute(route)) {
7788
+ const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
7789
+ if (wrapperFixed) {
7790
+ codeToWrite = normalized;
7791
+ autoFixes.push("normalized page wrapper to standard spacing");
7792
+ }
7793
+ }
7746
7794
  const allFixes = [...postFixes, ...autoFixes];
7747
7795
  if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
7748
- if (widthWasFixed2) allFixes.push("enforced max-w-7xl width consistency");
7749
7796
  if (allFixes.length > 0) {
7750
7797
  console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
7751
7798
  allFixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.3.12",
6
+ "version": "0.5.0",
7
7
  "description": "CLI interface for Coherent Design Method",
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",
@@ -33,15 +33,8 @@
33
33
  ],
34
34
  "author": "Coherent Design Method",
35
35
  "license": "MIT",
36
- "scripts": {
37
- "dev": "tsup --watch",
38
- "build": "tsup",
39
- "typecheck": "tsc --noEmit",
40
- "test": "vitest"
41
- },
42
36
  "dependencies": {
43
37
  "@anthropic-ai/sdk": "^0.32.0",
44
- "@getcoherent/core": "workspace:*",
45
38
  "chalk": "^5.3.0",
46
39
  "chokidar": "^4.0.1",
47
40
  "commander": "^11.1.0",
@@ -49,12 +42,19 @@
49
42
  "open": "^10.1.0",
50
43
  "ora": "^7.0.1",
51
44
  "prompts": "^2.4.2",
52
- "zod": "^3.22.4"
45
+ "zod": "^3.22.4",
46
+ "@getcoherent/core": "0.5.0"
53
47
  },
54
48
  "devDependencies": {
55
49
  "@types/node": "^20.11.0",
56
50
  "@types/prompts": "^2.4.9",
57
51
  "tsup": "^8.0.1",
58
52
  "typescript": "^5.3.3"
53
+ },
54
+ "scripts": {
55
+ "dev": "tsup --watch",
56
+ "build": "tsup",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest"
59
59
  }
60
- }
60
+ }