# @tailor-platform/app-shell-vite-plugin

## 0.2.3

### Patch Changes

- 3d48b24: Remove `loader` from file-based page definitions (`Page.appShellPageProps`). This removes an accidentally exposed incomplete API and makes `guards` the single source of page-level route behavior in file-based routing.

## 0.2.2

### Patch Changes

- ee7f7c7: Updated [ts-morph](https://www.npmjs.com/package/ts-morph) (^27.0.2 -> ^28.0.0)

## 0.2.1

### Patch Changes

- 01984ee: Update README to document the `entrypoint` option, `[...slug]` catch-all path conversion, and the current `AppShell.WithPages` implementation.

## 0.2.0

### Minor Changes

- 01a2249: Add `entrypoint` option to `appShellRoutes()` plugin. When specified, only imports from the entrypoint file are intercepted and replaced with the pages-injected AppShell, eliminating circular module dependencies entirely. It is recommended to set this option to avoid potential TDZ errors caused by circular imports in page components.

  ```ts
  appShellRoutes({
    entrypoint: "src/App.tsx",
  });
  ```

## 0.1.0

### Minor Changes

- e7fa8ec: Add file-based routing support via new Vite plugin

  File-based routing allows defining pages by placing components in a directory structure, eliminating the need for explicit `defineModule()` and `defineResource()` calls.

  ### Why file-based routing?

  **URL-First Design is Already the Norm** - Most projects naturally align their module/resource hierarchy with URL paths. A "purchasing" module at `/purchasing` with an "orders" resource at `/purchasing/orders` is the intuitive choice. The previous API required manually wiring up this structure even though the mapping was already implicit.

  **AI-Friendly Development** - By adopting file-based routing patterns pioneered by Next.js, AI tools can understand and navigate your codebase with less context. Code generation becomes more predictable and the established convention serves as shared knowledge between humans and AI.

  **Providing Rails, Not Just Flexibility** - The legacy `defineModule()`/`defineResource()` API gave flexibility but offered few conventions for directory structure, hierarchy management, or file naming. File-based routing provides an opinionated, battle-tested convention.

  Importantly, this is implemented as a Vite plugin layer on top of the existing programmatic API. Projects requiring non-standard routing can still use `defineModule()`/`defineResource()` directly.

  ### Backward Compatibility

  File-based routing is a **recommended opt-in** feature. The legacy declarative API (`defineModule()`/`defineResource()`) remains fully supported and will continue to work. You can choose either approach per project, though mixing both in the same application is not supported.

  ### Vite Plugin

  The new `@tailor-platform/app-shell-vite-plugin` package provides file-based routing support:

  ```typescript
  // vite.config.ts
  import { appShellRoutes } from "@tailor-platform/app-shell-vite-plugin";

  export default defineConfig({
    plugins: [
      react(),
      appShellRoutes(), // scans src/pages by default
    ],
  });
  ```

  Under the hood, the plugin:

  1. **Scans pages** - Finds `page.tsx` files in `src/pages` and builds a route tree
  2. **Generates virtual module** - Creates `virtual:app-shell-pages` with all discovered pages
  3. **Auto-injects pages** - Intercepts `@tailor-platform/app-shell` imports and wraps `AppShell` with `AppShell.WithPages(pages)`
  4. **Validates at build time** - Uses ts-morph AST analysis to validate `appShellPageProps`
  5. **Supports HMR** - Watches for page changes and triggers hot reload

  No manual wiring needed—just import `AppShell` as usual and pages are automatically available.

  ### Defining Pages

  ```tsx
  // src/pages/dashboard/page.tsx
  import type { AppShellPageProps } from "@tailor-platform/app-shell";

  const DashboardPage = () => <div>Dashboard</div>;

  DashboardPage.appShellPageProps = {
    meta: { title: "Dashboard" },
    guards: [authGuard],
  } satisfies AppShellPageProps;

  export default DashboardPage;
  ```

  ### Type-safe Routes (Optional)

  ```typescript
  // vite.config.ts
  appShellRoutes({ generateTypedRoutes: true });
  ```

  When enabled, the plugin generates `src/routes.generated.ts` containing:

  - `GeneratedRouteParams` type mapping all routes to their parameter types
  - `paths` helper with a type-safe `for()` method for building URLs
  - Module augmentation to register route types with app-shell

  ```tsx
  // Auto-generated: src/routes.generated.ts
  export type GeneratedRouteParams = {
    "/": {};
    "/dashboard": {};
    "/dashboard/orders/:id": { id: string };
  };
  export const paths = createTypedPaths<GeneratedRouteParams>();

  // Usage - TypeScript enforces correct params
  import { paths } from "./routes.generated";

  paths.for("/dashboard"); // ✓ OK
  paths.for("/dashboard/orders/:id", { id: "123" }); // ✓ OK → "/dashboard/orders/123"
  paths.for("/dashboard/orders/:id"); // ✗ Error: missing 'id'
  paths.for("/invalid-route"); // ✗ Error: route doesn't exist
  ```

  ### Breaking Change: Guard/Loader Cascade Behavior

  In the legacy `defineModule()`/`defineResource()` API, guards and loaders defined at the module level were automatically cascaded to all child resources. This automatic cascade behavior has been removed in both the legacy API and file-based routing—**guards and loaders are no longer automatically inherited**. Each resource or page must explicitly define its own guards and loaders.

  **Before (legacy API):**

  ```tsx
  defineModule({
    path: "/dashboard",
    guards: [authGuard], // Applied to all child resources automatically
    resources: [
      defineResource({ path: "/orders", element: <Orders /> }), // authGuard applied
      defineResource({ path: "/reports", element: <Reports /> }), // authGuard applied
    ],
  });
  ```

  **After (legacy API):**

  ```tsx
  defineModule({
    path: "/dashboard",
    guards: [authGuard],
    resources: [
      defineResource({
        path: "/orders",
        guards: [authGuard],
        element: <Orders />,
      }), // Must be explicitly defined
      defineResource({
        path: "/reports",
        guards: [authGuard],
        element: <Reports />,
      }), // Must be explicitly defined
    ],
  });
  ```

  Note: File-based routing, introduced in this release, also does not support guard/loader cascading. Each page must define its own guards and loaders explicitly:

  ```tsx
  // src/pages/dashboard/orders/page.tsx
  OrdersPage.appShellPageProps = {
    guards: [authGuard], // Must be explicitly defined
  } satisfies AppShellPageProps;

  // src/pages/dashboard/reports/page.tsx
  ReportsPage.appShellPageProps = {
    guards: [authGuard], // Must be explicitly defined
  } satisfies AppShellPageProps;
  ```

  **Rationale:** Explicit guard/loader definitions per resource/page improve code clarity and make it easier to understand the security requirements of each route at a glance.

  **Migration tip:** If you need the previous cascading behavior, compose your guards array explicitly:

  ```tsx
  // src/guards.ts
  export const requireAuth = [authGuard];
  export const requireAdmin = [authGuard, adminRoleGuard];

  // src/pages/dashboard/orders/page.tsx
  import { requireAuth } from "@/guards";

  OrdersPage.appShellPageProps = {
    guards: [...requireAuth, canViewOrders],
  } satisfies AppShellPageProps;

  // src/pages/admin/users/page.tsx
  import { requireAdmin } from "@/guards";

  UsersPage.appShellPageProps = {
    guards: [...requireAdmin],
  } satisfies AppShellPageProps;
  ```

  ## Breaking Change: Module without component requires guards

  As part of the ongoing effort to decouple navigation and routing (aligned with file-based routing), the automatic redirect behavior for modules without a `component` has been removed.

  Previously, a module without a `component` would automatically redirect to the first visible resource. However, in file-based routing, the resource hierarchy is determined ad-hoc by the vite-plugin based on directory structure, making this implicit redirect behavior inconsistent and unpredictable. To maintain consistency across both explicit and file-based routing, this behavior has been removed.

  If a module is defined without both `component` and `guards`, an error will be thrown at runtime. You must provide at least one of them.

  ```tsx
  // Before: automatic redirect to first visible resource
  defineModule({
    path: "reports",
    resources: [salesResource, usersResource],
  });

  // After: explicit redirect via guards
  defineModule({
    path: "reports",
    guards: [() => redirectTo("sales")],
    resources: [salesResource, usersResource],
  });

  // Error: defining a module without both component and guards will throw
  defineModule({
    path: "reports",
    resources: [salesResource, usersResource],
  }); // => throws an error
  ```
