proteum 2.1.9 → 2.2.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 (79) hide show
  1. package/.codex/environments/environment.toml +11 -0
  2. package/AGENTS.md +25 -11
  3. package/README.md +19 -9
  4. package/agents/project/AGENTS.md +165 -120
  5. package/agents/project/CODING_STYLE.md +1 -1
  6. package/agents/project/app-root/AGENTS.md +16 -0
  7. package/agents/project/client/AGENTS.md +5 -5
  8. package/agents/project/client/pages/AGENTS.md +13 -13
  9. package/agents/project/diagnostics.md +19 -10
  10. package/agents/project/optimizations.md +5 -6
  11. package/agents/project/root/AGENTS.md +295 -0
  12. package/agents/project/server/routes/AGENTS.md +2 -2
  13. package/agents/project/server/services/AGENTS.md +4 -2
  14. package/agents/project/tests/AGENTS.md +2 -2
  15. package/cli/app/index.ts +31 -7
  16. package/cli/commands/configure.ts +226 -0
  17. package/cli/commands/dev.ts +0 -2
  18. package/cli/commands/diagnose.ts +33 -1
  19. package/cli/commands/explain.ts +1 -1
  20. package/cli/commands/migrate.ts +51 -0
  21. package/cli/commands/orient.ts +169 -0
  22. package/cli/commands/perf.ts +8 -1
  23. package/cli/commands/verify.ts +1003 -49
  24. package/cli/compiler/artifacts/manifest.ts +4 -4
  25. package/cli/compiler/artifacts/routing.ts +2 -2
  26. package/cli/compiler/artifacts/services.ts +12 -3
  27. package/cli/compiler/client/index.ts +65 -19
  28. package/cli/compiler/common/files/style.ts +47 -2
  29. package/cli/compiler/common/generatedRouteModules.ts +31 -38
  30. package/cli/compiler/common/index.ts +10 -0
  31. package/cli/compiler/common/proteumManifest.ts +1 -0
  32. package/cli/compiler/server/index.ts +34 -9
  33. package/cli/context.ts +6 -1
  34. package/cli/index.ts +7 -8
  35. package/cli/migrate/pageContract.ts +516 -0
  36. package/cli/paths.ts +47 -6
  37. package/cli/presentation/commands.ts +100 -10
  38. package/cli/presentation/devSession.ts +4 -6
  39. package/cli/presentation/help.ts +2 -2
  40. package/cli/presentation/ink.ts +10 -5
  41. package/cli/presentation/welcome.ts +2 -4
  42. package/cli/runtime/commands.ts +94 -1
  43. package/cli/scaffold/index.ts +2 -2
  44. package/cli/scaffold/templates.ts +4 -2
  45. package/cli/utils/agents.ts +273 -58
  46. package/client/dev/profiler/index.tsx +3 -2
  47. package/client/router.ts +10 -2
  48. package/client/services/router/index.tsx +6 -22
  49. package/common/dev/connect.ts +20 -4
  50. package/common/dev/console.ts +7 -0
  51. package/common/dev/contractsDoctor.ts +354 -0
  52. package/common/dev/diagnostics.ts +10 -7
  53. package/common/dev/inspection.ts +830 -38
  54. package/common/dev/performance.ts +19 -5
  55. package/common/dev/profiler.ts +1 -0
  56. package/common/dev/proteumManifest.ts +5 -4
  57. package/common/dev/requestTrace.ts +12 -1
  58. package/common/router/contracts.ts +8 -11
  59. package/common/router/index.ts +2 -2
  60. package/common/router/pageData.ts +72 -0
  61. package/common/router/register.ts +10 -46
  62. package/common/router/response/page.ts +28 -16
  63. package/docs/dev-sessions.md +8 -4
  64. package/docs/diagnostics.md +77 -11
  65. package/docs/migrate-from-2.1.3.md +388 -0
  66. package/docs/request-tracing.md +25 -6
  67. package/package.json +6 -1
  68. package/scripts/update-codex-agents.ts +2 -2
  69. package/server/app/container/console/index.ts +11 -1
  70. package/server/app/container/trace/index.ts +117 -0
  71. package/server/app/devDiagnostics.ts +1 -1
  72. package/server/app/index.ts +5 -1
  73. package/server/services/auth/index.ts +9 -0
  74. package/server/services/router/index.ts +64 -14
  75. package/server/services/router/request/api.ts +7 -1
  76. package/server/services/router/response/index.ts +8 -28
  77. package/types/global/vendors.d.ts +12 -0
  78. package/types/vendors.d.ts +12 -0
  79. package/common/router/pageSetup.ts +0 -51
@@ -7,6 +7,8 @@ import type {
7
7
  TTraceSqlQuery,
8
8
  TTraceSummaryValue,
9
9
  } from './requestTrace';
10
+ import { buildRequestChain, explainOwner, type TDiagnoseChainItem } from './inspection';
11
+ import type { TProteumManifest } from './proteumManifest';
10
12
 
11
13
  export const perfGroupByValues = ['path', 'route', 'controller'] as const;
12
14
  export const perfWindowPresets = ['1h', '6h', '24h', 'today', 'yesterday'] as const;
@@ -38,11 +40,14 @@ export type TRequestPerfCall = {
38
40
  export type TRequestPerfSql = {
39
41
  callerLabel: string;
40
42
  durationMs: number;
43
+ fingerprint?: string;
41
44
  id: string;
42
45
  kind: TTraceSqlQuery['kind'];
46
+ connectedNamespace?: string;
43
47
  model?: string;
44
48
  operation: string;
45
49
  query: string;
50
+ serviceLabel?: string;
46
51
  target?: string;
47
52
  };
48
53
 
@@ -53,6 +58,7 @@ export type TRequestPerformance = {
53
58
  callDurationMs: number;
54
59
  capture: TRequestTrace['capture'];
55
60
  controllerLabel: string;
61
+ chain?: TDiagnoseChainItem[];
56
62
  cpuSystemMs?: number;
57
63
  cpuTotalMs?: number;
58
64
  cpuUserMs?: number;
@@ -321,12 +327,16 @@ const formatCallLabel = (call: TTraceCall) => {
321
327
  }
322
328
 
323
329
  const reference = `${call.method} ${call.path}`.trim();
330
+ if (call.serviceLabel && call.label && reference) return `${call.serviceLabel} -> ${call.label} (${reference})`;
331
+ if (call.serviceLabel && reference) return `${call.serviceLabel} -> ${reference}`;
324
332
  if (call.label && reference) return `${call.label} (${reference})`;
325
333
  return call.label || reference || call.origin;
326
334
  };
327
335
 
328
336
  const formatSqlCallerLabel = (query: TTraceSqlQuery) => {
329
337
  const reference = `${query.callerMethod} ${query.callerPath}`.trim();
338
+ if (query.serviceLabel && query.callerLabel && reference) return `${query.serviceLabel} -> ${query.callerLabel} (${reference})`;
339
+ if (query.serviceLabel && reference) return `${query.serviceLabel} -> ${reference}`;
330
340
  if (query.callerLabel && reference) return `${query.callerLabel} (${reference})`;
331
341
  return query.callerLabel || reference || query.operation;
332
342
  };
@@ -472,7 +482,7 @@ const summarizeProfiles = (profiles: TRequestPerformance[]): TPerfTopSummary =>
472
482
  };
473
483
  };
474
484
 
475
- export const buildRequestPerformance = (trace: TRequestTrace): TRequestPerformance => {
485
+ export const buildRequestPerformance = (trace: TRequestTrace, manifest?: TProteumManifest): TRequestPerformance => {
476
486
  const performance = trace.performance;
477
487
  const cpuUserMs = performance ? performance.cpu.userMicros / 1000 : undefined;
478
488
  const cpuSystemMs = performance ? performance.cpu.systemMicros / 1000 : undefined;
@@ -503,6 +513,7 @@ export const buildRequestPerformance = (trace: TRequestTrace): TRequestPerforman
503
513
  callDurationMs,
504
514
  capture: trace.capture,
505
515
  controllerLabel: findControllerLabel(trace),
516
+ chain: manifest ? buildRequestChain({ manifest, owner: explainOwner(manifest, trace.path), request: trace }) : undefined,
506
517
  cpuSystemMs,
507
518
  cpuTotalMs,
508
519
  cpuUserMs,
@@ -534,11 +545,14 @@ export const buildRequestPerformance = (trace: TRequestTrace): TRequestPerforman
534
545
  .map((query) => ({
535
546
  callerLabel: formatSqlCallerLabel(query),
536
547
  durationMs: query.durationMs,
548
+ fingerprint: query.fingerprint,
537
549
  id: query.id,
538
550
  kind: query.kind,
551
+ connectedNamespace: query.connectedNamespace,
539
552
  model: query.model,
540
553
  operation: query.operation,
541
554
  query: query.query,
555
+ serviceLabel: query.serviceLabel,
542
556
  target: query.target,
543
557
  })),
544
558
  htmlBytes: readNumber(renderEnd?.details.htmlLength),
@@ -582,7 +596,7 @@ export const buildPerfTopResponse = ({
582
596
  }): TPerfTopResponse => {
583
597
  const selectedGroupBy = perfGroupByValues.includes(groupBy) ? groupBy : 'path';
584
598
  const { requests: filteredRequests, window } = selectRequestsInWindow(requests, since);
585
- const profiles = filteredRequests.map(buildRequestPerformance);
599
+ const profiles = filteredRequests.map((trace) => buildRequestPerformance(trace));
586
600
  const groups = new Map<string, TRequestPerformance[]>();
587
601
 
588
602
  for (const profile of profiles) {
@@ -738,7 +752,7 @@ export const buildPerfMemoryResponse = ({
738
752
  }): TPerfMemoryResponse => {
739
753
  const selectedGroupBy = perfGroupByValues.includes(groupBy) ? groupBy : 'path';
740
754
  const { requests: filteredRequests, window } = selectRequestsInWindow(requests, since);
741
- const profiles = filteredRequests.map(buildRequestPerformance);
755
+ const profiles = filteredRequests.map((trace) => buildRequestPerformance(trace));
742
756
  const groups = new Map<string, TRequestPerformance[]>();
743
757
 
744
758
  for (const profile of profiles) {
@@ -793,7 +807,7 @@ export const buildPerfMemoryResponse = ({
793
807
  };
794
808
  };
795
809
 
796
- export const resolvePerfRequest = (requests: TRequestTrace[], requestOrPath: string) => {
810
+ export const resolvePerfRequest = (requests: TRequestTrace[], requestOrPath: string, manifest?: TProteumManifest) => {
797
811
  const normalized = requestOrPath.trim();
798
812
  if (!normalized) throw new Error('Perf request id or path is required.');
799
813
 
@@ -805,5 +819,5 @@ export const resolvePerfRequest = (requests: TRequestTrace[], requestOrPath: str
805
819
  throw new Error(`Could not find a traced request for "${requestOrPath}".`);
806
820
  }
807
821
 
808
- return buildRequestPerformance(request);
822
+ return buildRequestPerformance(request, manifest);
809
823
  };
@@ -4,6 +4,7 @@ export const profilerTraceRequestIdHeader = 'x-proteum-trace-request-id';
4
4
  export const profilerSessionIdHeader = 'x-proteum-profiler-session-id';
5
5
  export const profilerOriginHeader = 'x-proteum-profiler-origin';
6
6
  export const profilerParentRequestIdHeader = 'x-proteum-profiler-parent-request-id';
7
+ export const profilerConnectedNamespaceHeader = 'x-proteum-profiler-connected-namespace';
7
8
 
8
9
  export type TProfilerUiState = 'expanded' | 'minimized' | 'pinned-handle';
9
10
  export type TProfilerPanel =
@@ -14,6 +14,7 @@ export type TProteumManifestDiagnostic = {
14
14
  message: string;
15
15
  filepath: string;
16
16
  sourceLocation?: TProteumManifestSourceLocation;
17
+ fixHint?: string;
17
18
  relatedFilepaths?: string[];
18
19
  };
19
20
 
@@ -86,7 +87,7 @@ export type TProteumManifestRoute = {
86
87
  invalidOptionKeys: string[];
87
88
  reservedOptionKeys: string[];
88
89
  optionsRaw?: string;
89
- hasSetup: boolean;
90
+ hasData: boolean;
90
91
  chunkId?: string;
91
92
  chunkFilepath?: string;
92
93
  scope: TProteumManifestScope;
@@ -101,7 +102,7 @@ export type TProteumManifestLayout = {
101
102
  };
102
103
 
103
104
  export type TProteumManifest = {
104
- version: 9;
105
+ version: 10;
105
106
  app: {
106
107
  root: string;
107
108
  coreRoot: string;
@@ -125,8 +126,8 @@ export type TProteumManifest = {
125
126
  };
126
127
  };
127
128
  conventions: {
128
- routeSetupOptionKeys: string[];
129
- reservedRouteSetupKeys: string[];
129
+ routeOptionKeys: string[];
130
+ reservedRouteOptionKeys: string[];
130
131
  };
131
132
  env: {
132
133
  source: string;
@@ -28,7 +28,6 @@ export const traceEventTypes = [
28
28
  'resolve.not-found',
29
29
  'controller.start',
30
30
  'controller.result',
31
- 'setup.options',
32
31
  'context.create',
33
32
  'page.data',
34
33
  'ssr.payload',
@@ -36,6 +35,8 @@ export const traceEventTypes = [
36
35
  'render.end',
37
36
  'response.send',
38
37
  'request.finish',
38
+ 'cache.hit',
39
+ 'cache.write',
39
40
  'error',
40
41
  ] as const;
41
42
 
@@ -81,6 +82,11 @@ export type TTraceCall = {
81
82
  fetcherId?: string;
82
83
  connectedProjectNamespace?: string;
83
84
  connectedControllerAccessor?: string;
85
+ ownerLabel?: string;
86
+ ownerFilepath?: string;
87
+ serviceLabel?: string;
88
+ cacheKey?: string;
89
+ cachePhase?: string;
84
90
  startedAt: string;
85
91
  finishedAt?: string;
86
92
  durationMs?: number;
@@ -107,6 +113,11 @@ export type TTraceSqlQuery = {
107
113
  kind: TTraceSqlQueryKind;
108
114
  model?: string;
109
115
  operation: string;
116
+ fingerprint?: string;
117
+ ownerLabel?: string;
118
+ ownerFilepath?: string;
119
+ serviceLabel?: string;
120
+ connectedNamespace?: string;
110
121
  paramsJson?: unknown;
111
122
  paramsText?: string;
112
123
  query: string;
@@ -3,24 +3,21 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Core
6
- import type { TFrontRenderer, TPageSetup } from './response/page';
6
+ import type { TFrontRenderer, TPageDataProvider } from './response/page';
7
7
  import type { TRouteOptions } from '.';
8
8
 
9
9
  /*----------------------------------
10
10
  - PUBLIC API
11
11
  ----------------------------------*/
12
12
 
13
- // Supported `Router.page(...)` registration signatures shared by client and compiler code.
13
+ // Supported `Router.page(...)` registration signature shared by client and compiler code.
14
14
  export type TRegisterPageArgs<TProvidedData extends {} = {}, TPageOptions extends {} = TRouteOptions> =
15
- | [path: string, renderer: TFrontRenderer<TProvidedData>]
16
- | [path: string, setup: TPageSetup<TProvidedData>, renderer: TFrontRenderer<TProvidedData>]
17
- | [path: string, options: Partial<TPageOptions>, renderer: TFrontRenderer<TProvidedData>]
18
- | [
19
- path: string,
20
- options: Partial<TPageOptions>,
21
- setup: TPageSetup<TProvidedData>,
22
- renderer: TFrontRenderer<TProvidedData>,
23
- ];
15
+ [
16
+ path: string,
17
+ options: Partial<TPageOptions>,
18
+ data: TPageDataProvider<TProvidedData> | null,
19
+ renderer: TFrontRenderer<TProvidedData>,
20
+ ];
24
21
 
25
22
  // Serialized SSR route description exchanged between build output and runtime.
26
23
  export type TSsrUnresolvedRoute<TKey = number | string> = { chunk: string } & (
@@ -18,7 +18,7 @@ import type { TAuthCheckInput, TAuthTrackingContext } from '@server/services/aut
18
18
  import type { TAppArrowFunction } from '@common/app';
19
19
 
20
20
  // Specfic
21
- import type { default as Page, TFrontRenderer, TPageSetup } from './response/page';
21
+ import type { default as Page, TFrontRenderer, TPageDataProvider } from './response/page';
22
22
 
23
23
  /*----------------------------------
24
24
  - TYPES: ROUTES
@@ -40,6 +40,7 @@ type TRouteBase<RouterContext = unknown, TResult = any> = {
40
40
 
41
41
  // Execute
42
42
  schema?: zod.ZodSchema;
43
+ data?: TPageDataProvider | null;
43
44
  controller: TRouteController<RouterContext, TResult>;
44
45
  options: TRouteOptions;
45
46
  };
@@ -76,7 +77,6 @@ export type TRouteOptions = {
76
77
  id?: string;
77
78
  filepath?: string;
78
79
  sourceLocation?: { line: number; column: number };
79
- setup?: TPageSetup;
80
80
 
81
81
  // Indexing
82
82
  bodyId?: string;
@@ -0,0 +1,72 @@
1
+ /*----------------------------------
2
+ - TYPES
3
+ ----------------------------------*/
4
+
5
+ import type { TAnyRoute, TRouteOptions } from '.';
6
+
7
+ export const routeOptionKeys = [
8
+ 'bodyId',
9
+ 'priority',
10
+ 'preload',
11
+ 'domain',
12
+ 'accept',
13
+ 'raw',
14
+ 'auth',
15
+ 'authTracking',
16
+ 'redirectLogged',
17
+ 'static',
18
+ 'whenStatic',
19
+ 'canonicalParams',
20
+ 'layout',
21
+ 'TESTING',
22
+ 'logging',
23
+ ] as const satisfies (keyof TRouteOptions)[];
24
+
25
+ export const reservedRouteOptionKeys = ['id', 'filepath', 'sourceLocation', 'data'] as const;
26
+
27
+ const routeOptionKeysSet = new Set<string>(routeOptionKeys);
28
+ const reservedRouteOptionKeysSet = new Set<string>(reservedRouteOptionKeys);
29
+ const reservedPageDataKeys = new Set<string>([
30
+ ...routeOptionKeys,
31
+ ...reservedRouteOptionKeys,
32
+ ...routeOptionKeys.map((key) => `_${key}`),
33
+ ...reservedRouteOptionKeys.map((key) => `_${key}`),
34
+ ]);
35
+
36
+ const formatRouteTarget = (route: TAnyRoute) => ('code' in route ? String(route.code) : route.path || '(unknown route)');
37
+
38
+ const formatRouteSource = (route: TAnyRoute) => {
39
+ const filepath = route.options.filepath || 'unknown file';
40
+ const line = route.options.sourceLocation?.line;
41
+ const column = route.options.sourceLocation?.column;
42
+
43
+ if (!line) return filepath;
44
+ if (!column) return `${filepath}:${line}`;
45
+ return `${filepath}:${line}:${column}`;
46
+ };
47
+
48
+ export const getRouteOptionKey = (key: string) => {
49
+ if (reservedRouteOptionKeysSet.has(key)) throw new Error(`"${key}" is a reserved Router.page option key.`);
50
+
51
+ return routeOptionKeysSet.has(key) ? (key as keyof TRouteOptions) : null;
52
+ };
53
+
54
+ export const validatePageDataResult = (route: TAnyRoute, result: unknown) => {
55
+ if (!result || typeof result !== 'object' || Array.isArray(result)) {
56
+ throw new Error(
57
+ `Router.page data for ${formatRouteTarget(route)} in ${formatRouteSource(route)} must return an object. ` +
58
+ `If the page has no data loader, pass null as the third argument.`,
59
+ );
60
+ }
61
+
62
+ for (const key of Object.keys(result)) {
63
+ if (!reservedPageDataKeys.has(key)) continue;
64
+
65
+ throw new Error(
66
+ `Router.page data for ${formatRouteTarget(route)} in ${formatRouteSource(route)} cannot return reserved key "${key}". ` +
67
+ `Move route behavior into the explicit Router.page(path, options, data, render) options argument.`,
68
+ );
69
+ }
70
+
71
+ return result as TObjetDonnees;
72
+ };
@@ -11,65 +11,29 @@ import type { TRegisterPageArgs } from './contracts';
11
11
 
12
12
  // types
13
13
  import type { TRouteOptions } from '.';
14
- import type { TFrontRenderer, TPageSetup } from './response/page';
14
+ import type { TPageDataProvider } from './response/page';
15
15
 
16
16
  /*----------------------------------
17
17
  - UTILS
18
18
  ----------------------------------*/
19
19
 
20
20
  export const getRegisterPageArgs = (...args: TRegisterPageArgs<any, TRouteOptions>) => {
21
- let path: string;
22
- let options: Partial<TRouteOptions> = {};
23
- let setup: TPageSetup | undefined;
24
- let renderer: TFrontRenderer;
21
+ const [path, options, data, renderer] = args;
25
22
 
26
- if (args.length === 2) {
27
- [path, renderer] = args;
28
- } else if (args.length === 3) {
29
- const [pathArg, optionsOrSetupArg, rendererArg] = args;
30
- path = pathArg;
31
- renderer = rendererArg;
23
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
24
+ throw new Error(`Router.page(${JSON.stringify(path)}) requires an explicit options object as its second argument.`);
25
+ }
32
26
 
33
- if (typeof optionsOrSetupArg === 'function') setup = optionsOrSetupArg;
34
- else options = optionsOrSetupArg;
35
- } else {
36
- const [pathArg, optionsArg, setupArg, rendererArg] = args;
37
- path = pathArg;
38
- options = optionsArg;
39
- setup = setupArg;
40
- renderer = rendererArg;
27
+ if (data !== null && typeof data !== 'function') {
28
+ throw new Error(
29
+ `Router.page(${JSON.stringify(path)}) requires a data function or null as its third argument.`,
30
+ );
41
31
  }
42
32
 
43
33
  // Automatic layout form the nearest _layout folder using static options only.
44
34
  const layout = getLayout(path, options);
45
35
 
46
- return { path, options, setup, renderer, layout };
47
- };
48
-
49
- export const getRegisterPageOptions = (...args: TRegisterPageArgs<any, TRouteOptions>) => {
50
- let path: string;
51
- let options: Partial<TRouteOptions> = {};
52
- let setup: TPageSetup | undefined;
53
- let renderer: TFrontRenderer;
54
-
55
- if (args.length === 2) {
56
- [path, renderer] = args;
57
- } else if (args.length === 3) {
58
- const [pathArg, optionsOrSetupArg, rendererArg] = args;
59
- path = pathArg;
60
- renderer = rendererArg;
61
-
62
- if (typeof optionsOrSetupArg === 'function') setup = optionsOrSetupArg;
63
- else options = optionsOrSetupArg;
64
- } else {
65
- const [pathArg, optionsArg, setupArg, rendererArg] = args;
66
- path = pathArg;
67
- options = optionsArg;
68
- setup = setupArg;
69
- renderer = rendererArg;
70
- }
71
-
72
- return { path, options, setup, renderer };
36
+ return { path, options, data: data as TPageDataProvider | null, renderer, layout };
73
37
  };
74
38
 
75
39
  export const buildRegex = (path: string) => {
@@ -8,27 +8,38 @@ import type { Thing } from 'schema-dts';
8
8
 
9
9
  // Core libs
10
10
  import type { ClientContext } from '@/client/context';
11
- import { ClientOrServerRouter, TErrorRoute, TPageErrorRoute, TPageRoute, TRoute } from '@common/router';
11
+ import { ClientOrServerRouter, TErrorRoute, TPageErrorRoute, TPageRoute, TRoute, TRouteOptions } from '@common/router';
12
12
  import type { TFetcher, TFetcherList } from '@common/router/request/api';
13
- import { splitRouteSetupResult } from '@common/router/pageSetup';
13
+ import { validatePageDataResult } from '@common/router/pageData';
14
14
 
15
15
  /*----------------------------------
16
16
  - TYPES
17
17
  ----------------------------------*/
18
18
 
19
- export type TPageSetupContext = ClientContext;
19
+ export type TPageDataContext = ClientContext;
20
20
 
21
21
  export type TPageRenderContext = With<ClientContext, 'page'>;
22
22
 
23
+ type TPageResponseContext = {
24
+ route: { options: Partial<TRouteOptions> };
25
+ request: {
26
+ url: string;
27
+ data: TObjetDonnees;
28
+ api: {
29
+ fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees>;
30
+ };
31
+ };
32
+ };
33
+
23
34
  export type TResolvedPageData<TProvidedData extends {} = {}> = {
24
35
  [Property in keyof TProvidedData]: TProvidedData[Property] extends TFetcher<infer TData>
25
36
  ? TData
26
37
  : Awaited<TProvidedData[Property]>;
27
38
  };
28
39
 
29
- // The function that prepares route config and SSR data before rendering.
30
- export type TPageSetup<TProvidedData extends {} = {}> = (
31
- context: TPageSetupContext & {
40
+ // The function that prepares SSR data before rendering.
41
+ export type TPageDataProvider<TProvidedData extends {} = {}> = (
42
+ context: TPageDataContext & {
32
43
  // URL query parameters
33
44
  // TODO: typings
34
45
  data: { [key: string]: string | number };
@@ -36,7 +47,7 @@ export type TPageSetup<TProvidedData extends {} = {}> = (
36
47
  ) => TProvidedData;
37
48
 
38
49
  export type TDataProvider<TProvidedData extends {} = TFetcherList> = (
39
- context: TPageSetupContext & { data: { [key: string]: PrimitiveValue } },
50
+ context: TPageDataContext & { data: { [key: string]: PrimitiveValue } },
40
51
  ) => TProvidedData;
41
52
 
42
53
  // The function that renders routes
@@ -68,7 +79,7 @@ const debug = false;
68
79
  export default abstract class PageResponse<
69
80
  TRouter extends ClientOrServerRouter = ClientOrServerRouter,
70
81
  TRouteLike extends TRoute | TErrorRoute = TPageRoute | TPageErrorRoute,
71
- TContext extends TPageRenderContext = TPageRenderContext,
82
+ TContext extends TPageResponseContext = TPageResponseContext,
72
83
  > {
73
84
  // Metadata
74
85
  public chunkId?: string;
@@ -100,18 +111,19 @@ export default abstract class PageResponse<
100
111
  this.url = context.request.url;
101
112
  }
102
113
 
103
- private resolveSetup() {
104
- const setup = this.route.options.setup;
105
- if (!setup) return { options: {}, data: {} };
114
+ private resolveDataProviderResult() {
115
+ const dataProvider = 'data' in this.route ? this.route.data : undefined;
116
+ if (!dataProvider) return {};
106
117
 
107
- const setupContext = { ...this.context, data: this.context.request.data } as Parameters<typeof setup>[0];
118
+ const dataContext = { ...this.context, data: this.context.request.data } as unknown as Parameters<
119
+ typeof dataProvider
120
+ >[0];
108
121
 
109
- return splitRouteSetupResult(setup(setupContext) || {});
122
+ return validatePageDataResult(this.route, dataProvider(dataContext));
110
123
  }
111
124
 
112
125
  private createFetchers() {
113
- const { options, data } = this.resolveSetup();
114
- this.route.options = { ...this.route.options, ...options };
126
+ const data = this.resolveDataProviderResult();
115
127
  this.chunkId = this.route.options.id;
116
128
 
117
129
  return data as TFetcherList;
@@ -126,7 +138,7 @@ export default abstract class PageResponse<
126
138
  const layoutContext = {
127
139
  ...this.context,
128
140
  data: this.context.request.data,
129
- } as Parameters<typeof this.layout.data>[0];
141
+ } as unknown as Parameters<typeof this.layout.data>[0];
130
142
  const fetchers = this.layout.data(layoutContext);
131
143
  this.fetchers = { ...this.fetchers, ...fetchers };
132
144
  }
@@ -1,6 +1,6 @@
1
1
  # Dev Sessions
2
2
 
3
- Proteum ships a dev-only auth bootstrap command so agents, Playwright runs, and local debugging can start from an authenticated state without driving the login UI.
3
+ Proteum ships a dev-only auth bootstrap command so `proteum verify browser`, Playwright runs, and local debugging can start from an authenticated state without driving the login UI.
4
4
 
5
5
  ## When To Use It
6
6
 
@@ -37,6 +37,7 @@ Behavior:
37
37
  - remote mode talks to an already running `proteum dev` instance
38
38
  - the command requires an explicit email and optionally asserts a role before returning the session
39
39
  - the command is available only in dev mode
40
+ - browser verification flows should keep browser state app-local and disposable through `proteum verify browser` or direct Playwright instead of reusing a shared temp profile
40
41
 
41
42
  ## Output Contract
42
43
 
@@ -67,20 +68,23 @@ curl -H "$(jq -r '.curlCookieHeader' session.json)" http://localhost:3101/api/Au
67
68
  ## Agent Guidance
68
69
 
69
70
  - Prefer `proteum session` over UI login automation when the goal is to test or debug protected application behavior.
71
+ - Prefer `proteum verify browser` for focused browser-visible verification. When lower-level control is required, use direct Playwright with a disposable profile.
70
72
  - Use UI login automation only when the auth UX itself is the feature under test.
71
73
  - Pair it with `proteum diagnose` for a fast protected-route summary, `proteum perf request` for a one-request timing breakdown, then use `proteum trace` when you need lower-level request events.
74
+ - Only the final verifier agent should usually run browser flows. Earlier agents should stay on `orient`, `verify owner`, `verify request`, and request-level diagnostics unless browser execution is required.
72
75
 
73
76
  Typical flow:
74
77
 
75
78
  ```bash
76
- proteum trace arm --capture deep --port 3101
79
+ proteum orient /dashboard
77
80
  proteum session admin@example.com --role ADMIN --port 3101 --json > session.json
78
- # add the returned cookie in Playwright, then load the protected page once
79
- proteum diagnose /dashboard --port 3101
81
+ proteum diagnose /dashboard --hit /dashboard --port 3101
80
82
  proteum perf request /dashboard --port 3101
81
83
  proteum trace latest --port 3101
82
84
  ```
83
85
 
86
+ When `proteum verify browser <path>` is available in the target app, it uses the same fresh per-run browser workspace model under `var/proteum/browser/<run-id>` and should be preferred over ad hoc shared Playwright profile reuse.
87
+
84
88
  ## Dev HTTP Endpoint
85
89
 
86
90
  The CLI uses the same dev-only endpoint exposed by the running app: