Files
FabledScribe/frontend/src/router/index.ts
T
bvandeusenandClaude Opus 5 3c0192d749
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 7s
CI & Build / integration (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 41s
CI & Build / Build & push image (push) Successful in 40s
feat(design-explorer): the gallery, including what isn't there
Milestone #251 step 3 (#2260). New /design view, reachable from a Palette icon
beside Trash and Settings — a meta-surface like /rules, so an icon rather than a
sixth primary nav link, but not hidden either, since somewhere the design system
is visible is the entire point.

Renders three things, and refuses to render a fourth:

- REAL components, imported not recreated: StatusBadge, PriorityBadge, TagPill.
- REAL tokens, read at runtime via readTokens() so the page shows the live
  cascade rather than what the stylesheet claims. Grouped, swatched where the
  value is a colour, flagged where the token is mode-aware.
- Rule 65's four button variants and rule 60's type scale, listed as SPEC and
  marked missing.

That last part is the point of the step rather than a shortfall of it. Step 3's
premise was "render the real components, not copies — a gallery of look-alikes
drifts from the app within a month and then lies." Buttons have no shared
implementation to import: .btn-primary is defined four separate times in four
<style scoped> blocks, and 30 of 54 SFCs carry their own button CSS (#2273).
Drawing a button here would have made this page the fifth copy — committing the
exact drift the surface exists to catch. Same for the type scale: the three
families load (rule 59) but rule 60's sizes and weights are not tokens, so there
is nothing to read and a rendered specimen would be invented.

So the gallery reports them as gaps. A design system nobody can point at is a
design system that isn't there, and saying so is more useful than a page that
looks complete.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
2026-07-30 14:41:06 -04:00

169 lines
4.2 KiB
TypeScript

import { createRouter, createWebHistory } from "vue-router";
import { useAuthStore } from "@/stores/auth";
const router = createRouter({
history: createWebHistory(),
routes: [
{
// The dashboard ("what to work on") is the landing page; Knowledge
// remains as the exhaustive "Browse" surface.
path: "/",
redirect: "/dashboard",
},
{
path: "/dashboard",
name: "dashboard",
component: () => import("@/views/DashboardView.vue"),
},
{
path: "/knowledge",
name: "knowledge",
component: () => import("@/views/KnowledgeView.vue"),
},
{
path: "/login",
name: "login",
component: () => import("@/views/LoginView.vue"),
meta: { public: true },
},
{
path: "/register",
name: "register",
component: () => import("@/views/RegisterView.vue"),
meta: { public: true },
},
{
path: "/forgot-password",
name: "forgot-password",
component: () => import("@/views/ForgotPasswordView.vue"),
meta: { public: true },
},
{
path: "/reset-password",
name: "reset-password",
component: () => import("@/views/ResetPasswordView.vue"),
meta: { public: true },
},
{
path: "/register-invite",
name: "register-invite",
component: () => import("@/views/RegisterInviteView.vue"),
meta: { public: true },
},
{
path: "/notes",
redirect: "/knowledge",
},
{
path: "/notes/new",
name: "note-new",
component: () => import("@/views/NoteEditorView.vue"),
},
{
path: "/notes/:id",
name: "note-view",
component: () => import("@/views/NoteViewerView.vue"),
},
{
path: "/notes/:id/edit",
name: "note-edit",
component: () => import("@/views/NoteEditorView.vue"),
},
{
path: "/snippets",
name: "snippets",
component: () => import("@/views/SnippetListView.vue"),
},
{
path: "/snippets/new",
name: "snippet-new",
component: () => import("@/views/SnippetEditorView.vue"),
},
{
path: "/snippets/:id",
name: "snippet-view",
component: () => import("@/views/SnippetDetailView.vue"),
},
{
path: "/snippets/:id/edit",
name: "snippet-edit",
component: () => import("@/views/SnippetEditorView.vue"),
},
{
path: "/graph",
name: "graph",
component: () => import("@/views/GraphView.vue"),
},
{
path: "/projects",
name: "projects",
component: () => import("@/views/ProjectListView.vue"),
},
{
path: "/projects/:id",
name: "project-view",
component: () => import("@/views/ProjectView.vue"),
},
{
path: "/rules",
name: "rules",
component: () => import("@/views/RulesView.vue"),
},
{
// Meta-surface, same family as /rules: it describes the app rather than
// holding the operator's records.
path: "/design",
name: "design",
component: () => import("@/views/DesignView.vue"),
},
{
path: "/tasks",
redirect: "/",
},
{
path: "/tasks/new",
name: "task-new",
component: () => import("@/views/TaskEditorView.vue"),
},
{
path: "/tasks/:id",
name: "task-edit",
component: () => import("@/views/TaskEditorView.vue"),
},
{
path: "/shared",
name: "shared-with-me",
component: () => import("@/views/SharedWithMeView.vue"),
},
{
path: "/settings",
name: "settings",
component: () => import("@/views/SettingsView.vue"),
},
{
path: "/trash",
name: "trash",
component: () => import("@/views/TrashView.vue"),
},
{ path: "/admin/users", redirect: "/settings" },
{ path: "/admin/logs", redirect: "/settings" },
],
});
router.beforeEach(async (to) => {
if (to.meta.public) return;
const authStore = useAuthStore();
// Wait for initial auth check if still loading
if (authStore.loading && !authStore.isAuthenticated) {
await authStore.checkAuth();
}
if (!authStore.isAuthenticated) {
return { name: "login", query: { redirect: to.fullPath } };
}
});
export default router;