@atlashub/smartstack-mcp 1.13.0 → 1.14.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/dist/index.js CHANGED
@@ -558,7 +558,7 @@ var ConfigSchema = z.object({
558
558
  });
559
559
  var ValidateConventionsInputSchema = z.object({
560
560
  path: z.string().optional().describe("Project path to validate (default: SmartStack.app path)"),
561
- checks: z.array(z.enum(["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "all"])).default(["all"]).describe("Types of checks to perform")
561
+ checks: z.array(z.enum(["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "all"])).default(["all"]).describe("Types of checks to perform")
562
562
  });
563
563
  var CheckMigrationsInputSchema = z.object({
564
564
  projectPath: z.string().optional().describe("EF Core project path"),
@@ -914,7 +914,7 @@ var validateConventionsTool = {
914
914
  type: "array",
915
915
  items: {
916
916
  type: "string",
917
- enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "all"]
917
+ enum: ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs", "all"]
918
918
  },
919
919
  description: "Types of checks to perform",
920
920
  default: ["all"]
@@ -925,7 +925,7 @@ var validateConventionsTool = {
925
925
  async function handleValidateConventions(args, config) {
926
926
  const input = ValidateConventionsInputSchema.parse(args);
927
927
  const projectPath = input.path || config.smartstack.projectPath;
928
- const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers"] : input.checks;
928
+ const checks = input.checks.includes("all") ? ["tables", "migrations", "services", "namespaces", "entities", "tenants", "controllers", "layouts", "tabs"] : input.checks;
929
929
  logger.info("Validating conventions", { projectPath, checks });
930
930
  const result = {
931
931
  valid: true,
@@ -955,6 +955,12 @@ async function handleValidateConventions(args, config) {
955
955
  if (checks.includes("controllers")) {
956
956
  await validateControllerRoutes(structure, config, result);
957
957
  }
958
+ if (checks.includes("layouts")) {
959
+ await validateLayouts(structure, config, result);
960
+ }
961
+ if (checks.includes("tabs")) {
962
+ await validateTabs(structure, config, result);
963
+ }
958
964
  result.valid = result.errors.length === 0;
959
965
  result.summary = generateSummary(result, checks);
960
966
  return formatResult(result);
@@ -1385,6 +1391,132 @@ async function validateControllerRoutes(structure, _config, result) {
1385
1391
  });
1386
1392
  }
1387
1393
  }
1394
+ async function validateLayouts(structure, _config, result) {
1395
+ if (!structure.web) {
1396
+ result.warnings.push({
1397
+ type: "warning",
1398
+ category: "layouts",
1399
+ message: "Web project not found, skipping layout validation"
1400
+ });
1401
+ return;
1402
+ }
1403
+ const layoutFiles = await findFiles("**/layouts/**/*.tsx", { cwd: structure.web });
1404
+ if (layoutFiles.length === 0) {
1405
+ result.warnings.push({
1406
+ type: "warning",
1407
+ category: "layouts",
1408
+ message: "No layout files found in web/src/layouts/"
1409
+ });
1410
+ return;
1411
+ }
1412
+ for (const file of layoutFiles) {
1413
+ const content = await readText(file);
1414
+ const fileName = path6.basename(file);
1415
+ const lines = content.split("\n");
1416
+ let hasMaxWidth = false;
1417
+ for (const line of lines) {
1418
+ if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
1419
+ if (/className.*max-w-/.test(line)) {
1420
+ hasMaxWidth = true;
1421
+ break;
1422
+ }
1423
+ }
1424
+ if (hasMaxWidth) {
1425
+ result.errors.push({
1426
+ type: "error",
1427
+ category: "layouts",
1428
+ message: `Layout "${fileName}" uses max-w-* constraint which limits content width`,
1429
+ file: path6.relative(structure.root, file),
1430
+ suggestion: "Remove max-w-* class. Content should occupy full available width."
1431
+ });
1432
+ }
1433
+ let hasStandardPadding = false;
1434
+ for (const line of lines) {
1435
+ if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
1436
+ if (/className.*lg:px-10/.test(line)) {
1437
+ hasStandardPadding = true;
1438
+ break;
1439
+ }
1440
+ }
1441
+ if (!hasStandardPadding) {
1442
+ result.warnings.push({
1443
+ type: "warning",
1444
+ category: "layouts",
1445
+ message: `Layout "${fileName}" may be missing standard horizontal padding`,
1446
+ file: path6.relative(structure.root, file),
1447
+ suggestion: "Use lg:px-10 for consistent horizontal padding across layouts"
1448
+ });
1449
+ }
1450
+ let hasScrollPattern = false;
1451
+ for (const line of lines) {
1452
+ if (line.trim().startsWith("//") || line.trim().startsWith("*")) continue;
1453
+ if (/className.*h-full/.test(line) && /className.*overflow-auto/.test(line)) {
1454
+ hasScrollPattern = true;
1455
+ break;
1456
+ }
1457
+ }
1458
+ if (!hasScrollPattern) {
1459
+ result.warnings.push({
1460
+ type: "warning",
1461
+ category: "layouts",
1462
+ message: `Layout "${fileName}" may be missing scroll container pattern`,
1463
+ file: path6.relative(structure.root, file),
1464
+ suggestion: "Use h-full overflow-auto for proper internal scrolling"
1465
+ });
1466
+ }
1467
+ }
1468
+ }
1469
+ async function validateTabs(structure, _config, result) {
1470
+ if (!structure.web) {
1471
+ result.warnings.push({
1472
+ type: "warning",
1473
+ category: "tabs",
1474
+ message: "Web project not found, skipping tab validation"
1475
+ });
1476
+ return;
1477
+ }
1478
+ const pageFiles = await findFiles("**/pages/**/*.tsx", { cwd: structure.web });
1479
+ let tabPagesCount = 0;
1480
+ let hookUsageCount = 0;
1481
+ for (const file of pageFiles) {
1482
+ const content = await readText(file);
1483
+ const fileName = path6.basename(file);
1484
+ const lines = content.split("\n");
1485
+ let hasTabPattern = false;
1486
+ for (const line of lines) {
1487
+ const trimmed = line.trim();
1488
+ if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*") || trimmed === "") {
1489
+ continue;
1490
+ }
1491
+ if (/<Tabs|<TabList|activeTab\s*[,=})]|setActiveTab\s*[({]/.test(line)) {
1492
+ hasTabPattern = true;
1493
+ break;
1494
+ }
1495
+ }
1496
+ if (hasTabPattern) {
1497
+ tabPagesCount++;
1498
+ const usesHook = content.includes("useTabNavigation");
1499
+ if (usesHook) {
1500
+ hookUsageCount++;
1501
+ } else {
1502
+ result.errors.push({
1503
+ type: "error",
1504
+ category: "tabs",
1505
+ message: `Page "${fileName}" has tabs but doesn't use useTabNavigation hook`,
1506
+ file: path6.relative(structure.root, file),
1507
+ suggestion: "Use useTabNavigation hook to sync tab state with URL: const { activeTab, setActiveTab } = useTabNavigation(defaultTab, VALID_TABS)"
1508
+ });
1509
+ }
1510
+ }
1511
+ }
1512
+ if (tabPagesCount > 0) {
1513
+ result.warnings.push({
1514
+ type: "warning",
1515
+ category: "tabs",
1516
+ message: `Tab summary: ${hookUsageCount}/${tabPagesCount} pages with tabs use useTabNavigation hook`
1517
+ });
1518
+ }
1519
+ }
1388
1520
  function generateSummary(result, checks) {
1389
1521
  const parts = [];
1390
1522
  parts.push(`Checks performed: ${checks.join(", ")}`);
@@ -10468,6 +10600,76 @@ All tenant-related tables use the \`tenant_\` prefix:
10468
10600
 
10469
10601
  ---
10470
10602
 
10603
+ ## 10. Frontend Conventions
10604
+
10605
+ ### Layout Standards
10606
+
10607
+ All context layouts MUST follow the AdminLayout pattern for consistent user experience.
10608
+
10609
+ #### Standard Structure
10610
+
10611
+ \`\`\`tsx
10612
+ <main className={\`pt-16 transition-all duration-300 \${
10613
+ showSidebar ? (isCollapsed ? 'lg:pl-16' : 'lg:pl-64') : ''
10614
+ }\`}>
10615
+ <div className="p-4 sm:p-6 lg:px-10 h-full overflow-auto">
10616
+ <Outlet />
10617
+ </div>
10618
+ </main>
10619
+ \`\`\`
10620
+
10621
+ #### Layout Rules
10622
+
10623
+ | Rule | Description |
10624
+ |------|-------------|
10625
+ | No \`max-w-*\` | Content MUST occupy full available width |
10626
+ | Horizontal padding | Use \`lg:px-10\` for consistent padding |
10627
+ | Scroll container | Use \`h-full overflow-auto\` for internal scrolling |
10628
+ | Sidebar conditional | \`showSidebar = !!currentAppCode\` |
10629
+
10630
+ ### Tab Navigation with URL
10631
+
10632
+ Pages with tabs MUST synchronize tab state with URL for shareable links and browser navigation.
10633
+
10634
+ #### useTabNavigation Hook
10635
+
10636
+ \`\`\`tsx
10637
+ import { useTabNavigation } from '@/hooks/useTabNavigation';
10638
+
10639
+ // 1. Define valid tabs
10640
+ const VALID_TABS = ['info', 'settings', 'permissions'] as const;
10641
+ type TabId = typeof VALID_TABS[number];
10642
+
10643
+ // 2. Use the hook
10644
+ const { activeTab, setActiveTab } = useTabNavigation<TabId>('info', VALID_TABS);
10645
+
10646
+ // 3. Use in JSX
10647
+ <button onClick={() => setActiveTab('settings')}>Settings</button>
10648
+ \`\`\`
10649
+
10650
+ #### URL Behavior
10651
+
10652
+ | URL | Active Tab |
10653
+ |-----|------------|
10654
+ | \`/page\` | Default tab (e.g., \`info\`) |
10655
+ | \`/page?tab=settings\` | \`settings\` |
10656
+ | \`/page?tab=invalid\` | Default tab (fallback) |
10657
+
10658
+ #### Hook Characteristics
10659
+
10660
+ - URL is clean for default tab (no \`?tab=\` parameter)
10661
+ - Invalid tabs fallback to default
10662
+ - Uses \`replace: true\` to avoid polluting browser history
10663
+
10664
+ ### Frontend Validation Checks
10665
+
10666
+ | Check | Description |
10667
+ |-------|-------------|
10668
+ | \`layouts\` | Verify no \`max-w-*\` in content wrapper, standard padding present |
10669
+ | \`tabs\` | Verify \`useTabNavigation\` hook usage for pages with tabs |
10670
+
10671
+ ---
10672
+
10471
10673
  ## Quick Reference
10472
10674
 
10473
10675
  | Category | Convention | Example |
@@ -10500,6 +10702,10 @@ All tenant-related tables use the \`tenant_\` prefix:
10500
10702
  | Validation | \`withValidation: true\` | FluentValidation validators |
10501
10703
  | Repository | \`withRepository: true\` | Interface + Implementation |
10502
10704
  | Migration naming | \`suggest_migration\` | {context}_v{version}_{seq}_{Desc} |
10705
+ | Layout wrapper | No \`max-w-*\` | Content fills available width |
10706
+ | Layout padding | \`lg:px-10\` | Standard horizontal padding |
10707
+ | Tab navigation | \`useTabNavigation\` hook | Sync tabs with URL |
10708
+ | Tab URL | \`?tab=name\` | Shareable deep links to tabs |
10503
10709
 
10504
10710
  ---
10505
10711