@ogygie/studio 0.5.0 → 0.5.2

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 (35) hide show
  1. package/dist/server.js +918 -0
  2. package/package.json +8 -8
  3. package/src/index.ts +0 -41
  4. package/src/server/app.ts +0 -84
  5. package/src/server/routes.test.ts +0 -164
  6. package/src/server/routes.ts +0 -87
  7. package/src/server/schemas.ts +0 -92
  8. package/src/server/service.test.ts +0 -305
  9. package/src/server/service.ts +0 -426
  10. package/src/server/start.ts +0 -80
  11. package/src/server/storage/local-adapter.test.ts +0 -52
  12. package/src/server/storage/local-adapter.ts +0 -67
  13. package/src/server/storage/memory-adapter.ts +0 -49
  14. package/src/server/storage/spec-storage.ts +0 -22
  15. package/src/ui/app.css +0 -3
  16. package/src/ui/features/manifest-view/ManifestView.test.tsx +0 -67
  17. package/src/ui/features/manifest-view/ManifestView.tsx +0 -80
  18. package/src/ui/features/spec-board/SpecBoard.test.tsx +0 -92
  19. package/src/ui/features/spec-board/SpecBoard.tsx +0 -198
  20. package/src/ui/features/spec-editor/MarkdownPreview.tsx +0 -49
  21. package/src/ui/features/spec-editor/SpecEditor.test.tsx +0 -79
  22. package/src/ui/features/spec-editor/SpecEditor.tsx +0 -237
  23. package/src/ui/features/spec-editor/frontmatter-fields.test.ts +0 -54
  24. package/src/ui/features/spec-editor/frontmatter-fields.ts +0 -85
  25. package/src/ui/features/spec-editor/markdown-blocks.test.ts +0 -37
  26. package/src/ui/features/spec-editor/markdown-blocks.ts +0 -82
  27. package/src/ui/index.html +0 -12
  28. package/src/ui/lib/fetchContract.ts +0 -40
  29. package/src/ui/lib/queries.ts +0 -69
  30. package/src/ui/main.tsx +0 -27
  31. package/src/ui/patterns/EmptyState.tsx +0 -24
  32. package/src/ui/patterns/ErrorBanner.tsx +0 -51
  33. package/src/ui/patterns/Modal.tsx +0 -38
  34. package/src/ui/router.tsx +0 -76
  35. package/src/ui/test-utils.tsx +0 -56
@@ -1,51 +0,0 @@
1
- /**
2
- * @intent Canonical error surface of the studio: an OgygieError displayed IN
3
- * FULL (code, message, file, cause, suggestedFix) — the spec forbids masking
4
- * or summarizing a spec error. Accepts any thrown value and degrades to its
5
- * message when it is not an OgygieError.
6
- */
7
- import type { OgygieError } from "@ogygie/core/errors";
8
- import { StudioApiError } from "../lib/fetchContract";
9
-
10
- function payloadOf(error: unknown): OgygieError | null {
11
- if (error instanceof StudioApiError) return error.payload;
12
- return null;
13
- }
14
-
15
- export function ErrorBanner({ error }: { error: unknown }) {
16
- const payload = payloadOf(error);
17
- if (payload === null) {
18
- const message = error instanceof Error ? error.message : String(error);
19
- return (
20
- <div role="alert" className="rounded-md border border-red-300 bg-red-50 p-4 text-sm text-red-900">
21
- {message}
22
- </div>
23
- );
24
- }
25
- return <OgygieErrorCard error={payload} />;
26
- }
27
-
28
- export function OgygieErrorCard({ error }: { error: OgygieError }) {
29
- return (
30
- <div role="alert" className="rounded-md border border-red-300 bg-red-50 p-4 text-sm text-red-900">
31
- <p className="font-mono text-xs font-bold uppercase tracking-wide">{error.code}</p>
32
- <p className="mt-1 font-medium">{error.message}</p>
33
- <dl className="mt-2 space-y-1 text-xs">
34
- {error.file != null && (
35
- <div className="flex gap-2">
36
- <dt className="shrink-0 font-semibold">Fichier</dt>
37
- <dd className="font-mono">{error.file}</dd>
38
- </div>
39
- )}
40
- <div className="flex gap-2">
41
- <dt className="shrink-0 font-semibold">Cause</dt>
42
- <dd className="font-mono break-all">{error.cause}</dd>
43
- </div>
44
- <div className="flex gap-2">
45
- <dt className="shrink-0 font-semibold">Correction suggérée</dt>
46
- <dd>{error.suggestedFix}</dd>
47
- </div>
48
- </dl>
49
- </div>
50
- );
51
- }
@@ -1,38 +0,0 @@
1
- /**
2
- * @intent Canonical modal of the studio (overlay div, no extra dependency):
3
- * explicit confirmation surfaces — spec creation, the draft → validated
4
- * approval act. Rendered only when open; closing is always an explicit action.
5
- */
6
- import type { ReactNode } from "react";
7
-
8
- export function Modal({
9
- open,
10
- title,
11
- children,
12
- onClose,
13
- }: {
14
- open: boolean;
15
- title: string;
16
- children: ReactNode;
17
- onClose: () => void;
18
- }) {
19
- if (!open) return null;
20
- return (
21
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-gray-900/40 p-4">
22
- <div role="dialog" aria-modal="true" aria-label={title} className="w-full max-w-md rounded-lg bg-white p-5 shadow-xl">
23
- <div className="flex items-start justify-between gap-4">
24
- <h2 className="text-base font-semibold text-gray-900">{title}</h2>
25
- <button
26
- type="button"
27
- onClick={onClose}
28
- className="rounded p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
29
- aria-label="Fermer"
30
- >
31
- ✕
32
- </button>
33
- </div>
34
- <div className="mt-3">{children}</div>
35
- </div>
36
- </div>
37
- );
38
- }
package/src/ui/router.tsx DELETED
@@ -1,76 +0,0 @@
1
- /**
2
- * @intent Code-defined routing of the studio (TanStack Router, canonical
3
- * stack): board (/), spec editor (/specs/$specId), read-only manifest view
4
- * (/manifest). Exported as a factory so tests mount the SAME tree on a memory
5
- * history.
6
- */
7
- import {
8
- Link,
9
- Outlet,
10
- createRootRoute,
11
- createRoute,
12
- createRouter,
13
- type RouterHistory,
14
- } from "@tanstack/react-router";
15
- import { SpecBoard } from "./features/spec-board/SpecBoard";
16
- import { SpecEditor } from "./features/spec-editor/SpecEditor";
17
- import { ManifestView } from "./features/manifest-view/ManifestView";
18
-
19
- function Layout() {
20
- return (
21
- <div className="min-h-screen bg-gray-100">
22
- <header className="border-b border-gray-200 bg-white">
23
- <nav className="mx-auto flex max-w-5xl items-center gap-6 px-4 py-3">
24
- <span className="text-sm font-bold tracking-wide text-gray-900">Ogygie Studio</span>
25
- <Link to="/" className="text-sm text-gray-600 hover:text-gray-900 [&.active]:font-semibold [&.active]:text-gray-900">
26
- Board
27
- </Link>
28
- <Link to="/manifest" className="text-sm text-gray-600 hover:text-gray-900 [&.active]:font-semibold [&.active]:text-gray-900">
29
- Manifeste
30
- </Link>
31
- </nav>
32
- </header>
33
- <main className="mx-auto max-w-5xl px-4 py-6">
34
- <Outlet />
35
- </main>
36
- </div>
37
- );
38
- }
39
-
40
- const rootRoute = createRootRoute({ component: Layout });
41
-
42
- const boardRoute = createRoute({
43
- getParentRoute: () => rootRoute,
44
- path: "/",
45
- component: SpecBoard,
46
- });
47
-
48
- const editorRoute = createRoute({
49
- getParentRoute: () => rootRoute,
50
- path: "/specs/$specId",
51
- component: function SpecEditorRoute() {
52
- const { specId } = editorRoute.useParams();
53
- return <SpecEditor specId={specId} />;
54
- },
55
- });
56
-
57
- const manifestRoute = createRoute({
58
- getParentRoute: () => rootRoute,
59
- path: "/manifest",
60
- component: ManifestView,
61
- });
62
-
63
- const routeTree = rootRoute.addChildren([boardRoute, editorRoute, manifestRoute]);
64
-
65
- export function createStudioRouter(history?: RouterHistory) {
66
- return createRouter({
67
- routeTree,
68
- ...(history === undefined ? {} : { history }),
69
- });
70
- }
71
-
72
- declare module "@tanstack/react-router" {
73
- interface Register {
74
- router: ReturnType<typeof createStudioRouter>;
75
- }
76
- }
@@ -1,56 +0,0 @@
1
- /**
2
- * @intent Shared bench of the UI tests: mounts the REAL router tree on a
3
- * memory history with a fresh Query client, and stubs global fetch with typed
4
- * route handlers — a UI test exercises the same screens the studio serves,
5
- * never a lookalike.
6
- */
7
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
8
- import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
9
- import { render } from "@testing-library/react";
10
- import { vi } from "vitest";
11
- import { createStudioRouter } from "./router";
12
-
13
- export interface FetchStubRoute {
14
- method?: string;
15
- path: string;
16
- status?: number;
17
- payload: unknown;
18
- }
19
-
20
- /** Stubs global fetch: first matching route wins; an unmatched call fails the
21
- * test loudly instead of hanging a query. */
22
- export function stubFetch(routes: FetchStubRoute[]): void {
23
- vi.stubGlobal("fetch", async (input: RequestInfo | URL, init?: RequestInit) => {
24
- const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
25
- const method = init?.method ?? "GET";
26
- const match = routes.find(
27
- (route) => route.path === url && (route.method ?? "GET") === method,
28
- );
29
- if (match === undefined) {
30
- throw new Error(`fetch stub: no route for ${method} ${url}`);
31
- }
32
- return new Response(JSON.stringify(match.payload), {
33
- status: match.status ?? 200,
34
- headers: { "content-type": "application/json" },
35
- });
36
- });
37
- }
38
-
39
- /** Stubs fetch with a promise that never settles — the loading state. */
40
- export function stubFetchPending(): void {
41
- vi.stubGlobal("fetch", () => new Promise<Response>(() => undefined));
42
- }
43
-
44
- export function renderStudio(initialPath: string) {
45
- const router = createStudioRouter(
46
- createMemoryHistory({ initialEntries: [initialPath] }),
47
- );
48
- const queryClient = new QueryClient({
49
- defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
50
- });
51
- return render(
52
- <QueryClientProvider client={queryClient}>
53
- <RouterProvider router={router} />
54
- </QueryClientProvider>,
55
- );
56
- }