@softize/opus 27.1.0 → 29.0.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 (64) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/bin/cli.mjs +68 -3
  3. package/bin/lib/check.mjs +139 -30
  4. package/bin/lib/copy.mjs +505 -67
  5. package/bin/lib/materialize.mjs +99 -1
  6. package/docs/adr/0015-action-size-follows-interaction-density.md +2 -0
  7. package/docs/adr/0028-schedules-run-actions-as-the-system-by-action-opt-in.md +1 -1
  8. package/docs/adr/0029-page-header-is-the-single-page-introduction.md +3 -1
  9. package/docs/adr/0037-design-data-lives-outside-the-contract.md +1 -1
  10. package/docs/adr/0060-surface-composition-separates-chrome-and-intro.md +161 -0
  11. package/docs/adr/0061-claude-md-imports-agents-md.md +41 -0
  12. package/docs/adr/README.md +4 -0
  13. package/docs/code-style.md +47 -8
  14. package/docs/protocol.md +1 -1
  15. package/docs/releasing.md +7 -0
  16. package/package.json +1 -1
  17. package/registry/instructions/opus.md +3 -1
  18. package/registry/skills/build-opus-ui/SKILL.md +16 -14
  19. package/registry/skills/build-opus-ui/references/evaluations.md +10 -0
  20. package/registry/skills/build-opus-ui/references/ui-patterns.md +17 -15
  21. package/registry/skills/maintain-opus-docs/SKILL.md +2 -1
  22. package/registry/skills/maintain-opus-docs/references/editorial-standard.md +9 -0
  23. package/registry/skills/maintain-opus-docs/references/evaluations.md +8 -0
  24. package/registry/templates/app/pnpm-workspace.yaml +1 -1
  25. package/src/core/system-actor.ts +1 -1
  26. package/src/ui/components/patterns/list.tsx +63 -54
  27. package/src/ui/components/patterns/page.tsx +146 -38
  28. package/src/ui/components/patterns/presentation.tsx +44 -59
  29. package/src/ui/components/patterns/structural-header.tsx +42 -6
  30. package/src/ui/components/patterns/surface-anatomy.ts +52 -22
  31. package/src/ui/components/patterns/surface.tsx +2 -3
  32. package/src/ui/components/primitives/control.ts +2 -2
  33. package/src/ui/components/primitives/dialog.tsx +7 -2
  34. package/src/ui/components/primitives/drawer.tsx +7 -2
  35. package/src/ui/components/primitives/icon-picker.tsx +271 -34
  36. package/src/ui/components/primitives/pagination.tsx +5 -4
  37. package/src/ui/components/primitives/tabs.tsx +15 -2
  38. package/src/ui/components/primitives/toggle.tsx +1 -1
  39. package/src/ui/docs/content/action-list.md +12 -3
  40. package/src/ui/docs/content/actions.md +1 -1
  41. package/src/ui/docs/content/auth.md +3 -3
  42. package/src/ui/docs/content/button.md +7 -7
  43. package/src/ui/docs/content/cli.md +6 -0
  44. package/src/ui/docs/content/communication.md +10 -8
  45. package/src/ui/docs/content/compositions.md +35 -23
  46. package/src/ui/docs/content/dialog.md +18 -6
  47. package/src/ui/docs/content/drawer.md +17 -5
  48. package/src/ui/docs/content/events.md +13 -13
  49. package/src/ui/docs/content/icon-picker.md +37 -19
  50. package/src/ui/docs/content/input.md +1 -1
  51. package/src/ui/docs/content/installation.md +4 -0
  52. package/src/ui/docs/content/log.md +1 -1
  53. package/src/ui/docs/content/page.md +169 -69
  54. package/src/ui/docs/content/pagination.md +7 -8
  55. package/src/ui/docs/content/presentation.md +4 -3
  56. package/src/ui/docs/content/queue.md +2 -2
  57. package/src/ui/docs/content/router.md +1 -1
  58. package/src/ui/docs/content/scheduler.md +15 -15
  59. package/src/ui/docs/content/surface.md +8 -5
  60. package/src/ui/docs/content/tabs.md +12 -4
  61. package/src/ui/docs/content/testing.md +4 -4
  62. package/src/ui/meta.ts +1 -1
  63. package/src/ui/react.tsx +9 -1
  64. package/src/vite/design.ts +1 -1
@@ -31,15 +31,27 @@ import {
31
31
  } from "./page-heading-context.tsx";
32
32
  import {
33
33
  StructuralHeader,
34
+ validateSurfaceIntro,
34
35
  structuralHeaderClasses,
35
36
  } from "./structural-header.tsx";
36
37
  import { PageState, PageStatePresenceProvider } from "./page-state.tsx";
37
- import { FillRoot, fillRegionClassName, useFillRegion } from "./fill-region.ts";
38
+ import { FillRoot, useFillRegion } from "./fill-region.ts";
38
39
 
39
- interface PageContextValue {}
40
+ import {
41
+ surfaceHeaderRowClasses,
42
+ surfaceRegionClasses,
43
+ type SurfaceLayout,
44
+ } from "./surface-anatomy.ts";
45
+
46
+ interface PageContextValue {
47
+ surfaceLayout: SurfaceLayout;
48
+ contentClassName?: string;
49
+ }
40
50
 
41
51
  const PageContext = createContext<PageContextValue | null>(null);
42
52
  const PageHeaderContext = createContext(false);
53
+ const PageIntroContext = createContext(false);
54
+ const PageBodyContext = createContext(false);
43
55
  const PageIntegralStateContext = createContext(false);
44
56
  const PageActionsTargetContext = createContext<HTMLElement | null>(null);
45
57
  interface PageShellContextValue {
@@ -101,7 +113,7 @@ export function PageShell({
101
113
  <PageShellContext.Provider value={shell}>
102
114
  <div
103
115
  data-slot="page-shell-content"
104
- className="min-h-0 min-w-0 flex-1 overflow-auto [&>*]:min-w-[64rem]"
116
+ className="flex min-h-0 min-w-0 flex-1 flex-col overflow-x-auto overflow-y-auto [&>*]:min-w-[64rem]"
105
117
  >
106
118
  {children}
107
119
  </div>
@@ -136,13 +148,17 @@ interface PageBaseProps extends Omit<
136
148
  HTMLAttributes<HTMLElement>,
137
149
  "children" | "title"
138
150
  > {
139
- /** Classes do container. Default: centralizado com `max-w-7xl` (80rem). */
151
+ /** Classes do container externo. O conteúdo do Body mantém o teto de 80rem. */
140
152
  className?: string;
153
+ /** Classes do conteúdo centralizado, incluindo largura máxima e alinhamento. */
154
+ contentClassName?: string;
141
155
  /**
142
- * `fill` limita a página à altura do shell para que regiões internas assumam a rolagem.
156
+ * `flow` mantém a rolagem no body; `fill` permite que regiões internas assumam a rolagem.
143
157
  * A altura chega sozinha a uma `ActionList` composta por `PageBody` e `Surface` (ADR 0024).
144
158
  */
145
159
  layout?: "flow" | "fill";
160
+ /** Divisão visual das regiões, independente da distribuição de altura. */
161
+ surfaceLayout?: SurfaceLayout;
146
162
  }
147
163
 
148
164
  interface PageShorthandProps extends PageBaseProps {
@@ -183,6 +199,7 @@ function countDirectChildrenOfType(
183
199
 
184
200
  function countPageBodyHeadings(node: ReactNode): {
185
201
  content: number;
202
+ intro: number;
186
203
  state: number;
187
204
  } {
188
205
  return Children.toArray(node).reduce(
@@ -196,6 +213,7 @@ function countPageBodyHeadings(node: ReactNode): {
196
213
  if (child.type === PageState && props.status !== "ready") {
197
214
  return {
198
215
  content: total.content,
216
+ intro: total.intro,
199
217
  state: total.state + 1,
200
218
  };
201
219
  }
@@ -205,10 +223,11 @@ function countPageBodyHeadings(node: ReactNode): {
205
223
  total.content +
206
224
  (child.type === Surface && props.level === 1 ? 1 : 0) +
207
225
  nested.content,
226
+ intro: total.intro + (child.type === PageIntro ? 1 : 0) + nested.intro,
208
227
  state: total.state + nested.state,
209
228
  };
210
229
  },
211
- { content: 0, state: 0 },
230
+ { content: 0, intro: 0, state: 0 },
212
231
  );
213
232
  }
214
233
 
@@ -229,12 +248,13 @@ export function Page({
229
248
  description,
230
249
  actions,
231
250
  layout = "flow",
251
+ surfaceLayout = "sectioned",
252
+ contentClassName,
232
253
  className,
233
254
  children,
234
255
  ...props
235
256
  }: PageProps): ReactElement {
236
257
  const shell = useContext(PageShellContext);
237
- const insideShell = shell !== null;
238
258
  const shorthand = title !== undefined;
239
259
  const nodes = flattenPageHeaderChildren(children);
240
260
  const headers = nodes.filter(
@@ -298,6 +318,7 @@ export function Page({
298
318
  );
299
319
  }
300
320
  const bodyHeadingSources = bodies.reduce<{
321
+ intro: number;
301
322
  content: number;
302
323
  state: number;
303
324
  }>(
@@ -307,17 +328,20 @@ export function Page({
307
328
  );
308
329
  return {
309
330
  content: total.content + count.content,
331
+ intro: total.intro + count.intro,
310
332
  state: total.state + count.state,
311
333
  };
312
334
  },
313
- { content: 0, state: 0 },
335
+ { content: 0, intro: 0, state: 0 },
314
336
  );
315
337
  const structuralHeadingSources =
316
338
  headers.reduce<number>(
317
339
  (total, header) =>
318
340
  total + countDirectChildrenOfType(header as ReactElement, PageTitle),
319
341
  0,
320
- ) + bodyHeadingSources.content;
342
+ ) +
343
+ bodyHeadingSources.content +
344
+ bodyHeadingSources.intro;
321
345
  const headingSources =
322
346
  bodyHeadingSources.state > 0
323
347
  ? bodyHeadingSources.state + bodyHeadingSources.content
@@ -329,7 +353,7 @@ export function Page({
329
353
  );
330
354
  if (headingSources > 1 || (headingSources === 0 && !hasOpaqueBodyContent)) {
331
355
  throw new Error(
332
- "Page explícito exige exatamente um heading principal em PageHeader, Surface level={1} ou PageState ativo dentro de PageBody.",
356
+ "Page explícito exige exatamente um heading principal em PageHeader, PageIntro, Surface level={1} ou PageState ativo dentro de PageBody.",
333
357
  );
334
358
  }
335
359
  }
@@ -338,20 +362,24 @@ export function Page({
338
362
  if (integralState !== hasActiveIntegralState) return;
339
363
  if (headingCount.current !== 1) {
340
364
  throw new Error(
341
- "Page explícito exige exatamente um heading principal em PageHeader, Surface level={1} ou PageState ativo dentro de PageBody.",
365
+ "Page explícito exige exatamente um heading principal em PageHeader, PageIntro, Surface level={1} ou PageState ativo dentro de PageBody.",
342
366
  );
343
367
  }
344
368
  });
345
369
  const content = shorthand ? (
346
370
  <>
347
371
  <PageHeader>
348
- <PageTitle>{title}</PageTitle>
349
- {description !== undefined && (
350
- <PageDescription>{description}</PageDescription>
351
- )}
352
372
  {actions !== undefined && <PageActions>{actions}</PageActions>}
353
373
  </PageHeader>
354
- <PageBody>{children}</PageBody>
374
+ <PageBody>
375
+ <PageIntro>
376
+ <PageTitle>{title}</PageTitle>
377
+ {description !== undefined && (
378
+ <PageDescription>{description}</PageDescription>
379
+ )}
380
+ </PageIntro>
381
+ {children}
382
+ </PageBody>
355
383
  </>
356
384
  ) : (
357
385
  <>
@@ -364,29 +392,22 @@ export function Page({
364
392
  );
365
393
 
366
394
  return (
367
- <PageContext.Provider value={{}}>
395
+ <PageContext.Provider value={{ surfaceLayout, contentClassName }}>
368
396
  <PageIntegralStateContext.Provider value={integralState}>
369
397
  <PageStatePresenceProvider register={registerIntegralState}>
370
398
  <PageHeadingPresenceProvider register={registerHeading}>
371
399
  <main
372
400
  data-slot="page"
373
401
  data-layout={layout}
402
+ data-surface-layout={surfaceLayout}
374
403
  className={cn(
375
- "min-w-[64rem] flex-1",
376
- insideShell && "min-h-full",
377
- layout === "fill" &&
378
- "flex h-full min-h-0 flex-col overflow-hidden",
379
- integralState && "flex min-h-full flex-col",
404
+ "flex h-full min-h-0 min-w-[64rem] flex-1 flex-col overflow-hidden",
380
405
  )}
381
406
  {...props}
382
407
  >
383
408
  <div
384
409
  className={cn(
385
- "mx-auto max-w-7xl space-y-6 px-8 py-8",
386
- layout === "fill" &&
387
- "flex h-full min-h-0 w-full flex-1 flex-col",
388
- integralState &&
389
- "flex min-h-full w-full flex-1 flex-col space-y-0",
410
+ "flex h-full min-h-0 w-full flex-1 flex-col",
390
411
  className,
391
412
  )}
392
413
  >
@@ -455,6 +476,7 @@ export function PageHeader({
455
476
  if (
456
477
  structuralChildren.length === 0 &&
457
478
  mergedActions === null &&
479
+ localNavigation.length === 0 &&
458
480
  shell?.navigation === undefined
459
481
  ) {
460
482
  return <></>;
@@ -470,9 +492,25 @@ export function PageHeader({
470
492
  description: PageDescription,
471
493
  actions: PageActions,
472
494
  }}
473
- leadingPlacement="above"
495
+ leadingPlacement={
496
+ nodes.some(
497
+ (node) => isValidElement(node) && node.type === PageTitle,
498
+ ) ||
499
+ (localNavigation.length === 0 &&
500
+ shell?.navigation === undefined &&
501
+ !nodes.some(
502
+ (node) => isValidElement(node) && node.type === PageBack,
503
+ ))
504
+ ? "above"
505
+ : "inline"
506
+ }
474
507
  titleRequired={false}
475
- className={className}
508
+ rowClassName={surfaceHeaderRowClasses("page")}
509
+ className={cn(
510
+ "shrink-0",
511
+ surfaceRegionClasses("page", page!.surfaceLayout, "header"),
512
+ className,
513
+ )}
476
514
  {...props}
477
515
  >
478
516
  {shell?.navigation === undefined ? (
@@ -487,12 +525,62 @@ export function PageHeader({
487
525
  );
488
526
  }
489
527
 
528
+ /** Primeiro bloco do corpo, separado dos controles do cabeçalho. */
529
+ export function PageIntro({
530
+ className,
531
+ children,
532
+ ...props
533
+ }: HTMLAttributes<HTMLDivElement>): ReactElement {
534
+ requireParent(useContext(PageBodyContext), "PageIntro", "PageBody");
535
+ const integralState = useContext(PageIntegralStateContext);
536
+ if (integralState) return <></>;
537
+ return (
538
+ <PageIntroContext.Provider value>
539
+ <StructuralHeader
540
+ name="PageIntro"
541
+ slot="page-intro"
542
+ slots={{
543
+ title: PageTitle,
544
+ description: PageDescription,
545
+ actions: PageIntroActions,
546
+ }}
547
+ leadingPlacement="inline"
548
+ titleRequired
549
+ className={className}
550
+ {...props}
551
+ >
552
+ {children}
553
+ </StructuralHeader>
554
+ </PageIntroContext.Provider>
555
+ );
556
+ }
557
+
558
+ /** Ações locais do conteúdo, distintas das ações da superfície em PageActions. */
559
+ export function PageIntroActions({
560
+ className,
561
+ ...props
562
+ }: HTMLAttributes<HTMLDivElement>): ReactElement {
563
+ requireParent(useContext(PageIntroContext), "PageIntroActions", "PageIntro");
564
+ return (
565
+ <div
566
+ data-slot="page-intro-actions"
567
+ className={cn(structuralHeaderClasses.page.actions, className)}
568
+ {...props}
569
+ />
570
+ );
571
+ }
572
+
490
573
  export function PageTitle({
491
574
  className,
492
575
  ...props
493
576
  }: HTMLAttributes<HTMLHeadingElement>): ReactElement {
494
577
  const insideHeader = useContext(PageHeaderContext);
495
- requireParent(insideHeader, "PageTitle", "PageHeader");
578
+ const insideIntro = useContext(PageIntroContext);
579
+ requireParent(
580
+ insideHeader || insideIntro,
581
+ "PageTitle",
582
+ "PageHeader ou PageIntro",
583
+ );
496
584
  usePageHeadingPresence();
497
585
  return (
498
586
  <h1
@@ -508,7 +596,9 @@ export function PageDescription({
508
596
  className,
509
597
  ...props
510
598
  }: HTMLAttributes<HTMLParagraphElement>): ReactElement {
511
- requireParent(useContext(PageHeaderContext), "PageDescription", "PageHeader");
599
+ const header = useContext(PageHeaderContext);
600
+ const intro = useContext(PageIntroContext);
601
+ requireParent(header || intro, "PageDescription", "PageHeader ou PageIntro");
512
602
  return (
513
603
  <p
514
604
  data-slot="page-description"
@@ -574,7 +664,7 @@ export function PageBack({
574
664
  typeof children === "string" ? children : "a página anterior";
575
665
  const accessibleLabel = ariaLabel ?? `Voltar para ${destination}`;
576
666
  const back = (
577
- <Button asChild variant="ghost" size="icon">
667
+ <Button asChild variant="ghost" size="icon-sm">
578
668
  <a
579
669
  data-slot="page-back"
580
670
  aria-label={accessibleLabel}
@@ -592,25 +682,42 @@ export function PageBack({
592
682
 
593
683
  export function PageBody({
594
684
  className,
685
+ contentClassName,
595
686
  children,
596
687
  ...props
597
- }: HTMLAttributes<HTMLDivElement>): ReactElement {
688
+ }: HTMLAttributes<HTMLDivElement> & {
689
+ contentClassName?: string;
690
+ }): ReactElement {
598
691
  const page = useContext(PageContext);
599
- const integralState = useContext(PageIntegralStateContext);
600
692
  const fill = useFillRegion();
601
693
  requireParent(page !== null, "PageBody", "Page");
694
+ validateSurfaceIntro(children, PageIntro, "PageBody");
602
695
  return (
603
696
  <div
604
697
  data-slot="page-body"
605
698
  {...fill.attributes}
606
699
  className={cn(
607
- "flex flex-col gap-6 *:shrink-0",
608
- (integralState || fill.filling) && fillRegionClassName,
700
+ "flex min-h-0 flex-1 flex-col",
701
+ fill.filling ? "overflow-hidden" : "overflow-y-auto",
609
702
  className,
610
703
  )}
611
704
  {...props}
612
705
  >
613
- {fill.provide(children)}
706
+ <PageBodyContext.Provider value>
707
+ <div
708
+ data-slot="page-body-content"
709
+ {...fill.attributes}
710
+ className={cn(
711
+ "mx-auto w-full max-w-7xl",
712
+ surfaceRegionClasses("page", page!.surfaceLayout, "body"),
713
+ fill.filling ? "min-h-0 flex-1" : "min-h-full shrink-0",
714
+ page!.contentClassName,
715
+ contentClassName,
716
+ )}
717
+ >
718
+ {fill.provide(children)}
719
+ </div>
720
+ </PageBodyContext.Provider>
614
721
  </div>
615
722
  );
616
723
  }
@@ -625,7 +732,8 @@ export function PageFooter({
625
732
  <div
626
733
  data-slot="page-footer"
627
734
  className={cn(
628
- "mt-auto flex h-[3.75rem] shrink-0 items-center justify-end border-t bg-muted/30 p-3",
735
+ "mt-auto flex min-h-[3.75rem] shrink-0 items-center justify-end",
736
+ surfaceRegionClasses("page", page!.surfaceLayout, "footer"),
629
737
  className,
630
738
  )}
631
739
  {...props}
@@ -47,6 +47,7 @@ import {
47
47
  DialogContent,
48
48
  DialogFooter,
49
49
  DialogHeader,
50
+ DialogIntro,
50
51
  DialogNavigation,
51
52
  DialogTitle,
52
53
  } from "../primitives/dialog.tsx";
@@ -57,6 +58,7 @@ import {
57
58
  DrawerContent,
58
59
  DrawerFooter,
59
60
  DrawerHeader,
61
+ DrawerIntro,
60
62
  DrawerNavigation,
61
63
  DrawerTitle,
62
64
  } from "../primitives/drawer.tsx";
@@ -66,6 +68,7 @@ import {
66
68
  PageBody,
67
69
  PageFooter,
68
70
  PageHeader,
71
+ PageIntro,
69
72
  PageNavigation,
70
73
  PageTitle,
71
74
  useInsidePageShell,
@@ -114,11 +117,13 @@ const bodyClassName =
114
117
  * a lista do body é filha direta dele e assume `fill` sem classe no consumidor.
115
118
  */
116
119
  function PresentationBody({
120
+ page = false,
117
121
  layout,
118
122
  className,
119
123
  children,
120
124
  }: {
121
125
  layout: "flow" | "fill";
126
+ page?: boolean;
122
127
  className?: string;
123
128
  children: ReactNode;
124
129
  }): ReactElement {
@@ -128,7 +133,7 @@ function PresentationBody({
128
133
  data-slot="presentation-body"
129
134
  {...fill.attributes}
130
135
  className={cn(
131
- bodyClassName,
136
+ !page && bodyClassName,
132
137
  (layout === "fill" || fill.filling) && fillRegionClassName,
133
138
  className,
134
139
  )}
@@ -195,14 +200,14 @@ function PresentationFrame({
195
200
  </PresentationActions>
196
201
  );
197
202
  const body = (
198
- <PresentationBody layout={layout} className={bodyClassNameProp}>
203
+ <PresentationBody
204
+ page={surface === "page"}
205
+ layout={layout}
206
+ className={bodyClassNameProp}
207
+ >
199
208
  {typeof children === "function" ? children(footerTarget) : children}
200
209
  </PresentationBody>
201
210
  );
202
- // Viewport entre o PageBody e o conteúdo. Em `fill`, é coluna flexível para a altura
203
- // chegar ao conteúdo; a rolagem continua disponível como recurso de última instância.
204
- const viewportFillClassName =
205
- layout === "fill" ? fillRegionClassName : undefined;
206
211
  const withAssistant = (content: ReactElement): ReactElement =>
207
212
  assistantLabel === undefined || assistantPanel === undefined ? (
208
213
  content
@@ -248,18 +253,7 @@ function PresentationFrame({
248
253
  )}
249
254
  </PageHeader>
250
255
  )}
251
- <PageBody className="min-h-0 flex-1 overflow-hidden">
252
- {withAssistant(
253
- <div
254
- className={cn(
255
- "h-full overflow-x-hidden overflow-y-auto",
256
- viewportFillClassName,
257
- )}
258
- >
259
- {content}
260
- </div>,
261
- )}
262
- </PageBody>
256
+ <PageBody>{withAssistant(content)}</PageBody>
263
257
  {hasFooter ? <PageFooter>{renderFooterActions()}</PageFooter> : null}
264
258
  </Page>
265
259
  );
@@ -278,7 +272,6 @@ function PresentationFrame({
278
272
  {navigation === undefined ? null : (
279
273
  <PageNavigation>{navigation}</PageNavigation>
280
274
  )}
281
- {contentOwnsHeading ? null : <PageTitle>{title}</PageTitle>}
282
275
  {headerActions === undefined &&
283
276
  assistantAction === undefined ? null : (
284
277
  <PageActions>
@@ -290,36 +283,24 @@ function PresentationFrame({
290
283
  )}
291
284
  </PageHeader>
292
285
  )}
293
- <PageBody className="min-h-0 flex-1 overflow-hidden">
294
- {withAssistant(
295
- <div
296
- className={cn(
297
- "flex h-full min-h-0 flex-col overflow-x-hidden",
298
- layout === "fill" ? "overflow-y-hidden" : "overflow-y-auto",
299
- viewportFillClassName,
300
- )}
301
- >
302
- {body}
303
- </div>,
286
+ <PageBody>
287
+ {contentOwnsHeading ? null : (
288
+ <PageIntro>
289
+ <PageTitle>{title}</PageTitle>
290
+ </PageIntro>
304
291
  )}
292
+ {withAssistant(body)}
305
293
  </PageBody>
306
294
  {hasFooter ? <PageFooter>{renderFooterActions()}</PageFooter> : null}
307
295
  </Page>
308
296
  );
309
297
  }
310
298
  return (
311
- <Page
312
- layout={layout}
313
- className={cn(
314
- "flex min-h-full max-w-none flex-col space-y-0 px-0 py-0",
315
- className,
316
- )}
317
- >
299
+ <Page layout={layout} className={className} contentClassName="max-w-none">
318
300
  <PageHeader>
319
301
  {navigation === undefined ? null : (
320
302
  <PageNavigation>{navigation}</PageNavigation>
321
303
  )}
322
- <PageTitle className="text-lg leading-none">{title}</PageTitle>
323
304
  {headerActions === undefined &&
324
305
  assistantAction === undefined ? null : (
325
306
  <PageActions>
@@ -330,17 +311,11 @@ function PresentationFrame({
330
311
  </PageActions>
331
312
  )}
332
313
  </PageHeader>
333
- <PageBody className="min-h-0 flex-1 overflow-hidden">
334
- {withAssistant(
335
- <div
336
- className={cn(
337
- "h-full overflow-x-hidden overflow-y-auto px-8 py-8",
338
- viewportFillClassName,
339
- )}
340
- >
341
- {body}
342
- </div>,
343
- )}
314
+ <PageBody>
315
+ <PageIntro>
316
+ <PageTitle>{title}</PageTitle>
317
+ </PageIntro>
318
+ {withAssistant(body)}
344
319
  </PageBody>
345
320
  {hasFooter ? <PageFooter>{renderFooterActions()}</PageFooter> : null}
346
321
  </Page>
@@ -364,7 +339,6 @@ function PresentationFrame({
364
339
  {navigation === undefined ? null : (
365
340
  <DialogNavigation>{navigation}</DialogNavigation>
366
341
  )}
367
- <DialogTitle>{title}</DialogTitle>
368
342
  {headerActions === undefined &&
369
343
  assistantAction === undefined ? null : (
370
344
  <DialogActions>
@@ -375,7 +349,14 @@ function PresentationFrame({
375
349
  </DialogActions>
376
350
  )}
377
351
  </DialogHeader>
378
- {withAssistant(<DialogBody>{body}</DialogBody>)}
352
+ {withAssistant(
353
+ <DialogBody>
354
+ <DialogIntro>
355
+ <DialogTitle>{title}</DialogTitle>
356
+ </DialogIntro>
357
+ {body}
358
+ </DialogBody>,
359
+ )}
379
360
  {hasFooter ? (
380
361
  <DialogFooter>{renderFooterActions()}</DialogFooter>
381
362
  ) : null}
@@ -402,7 +383,6 @@ function PresentationFrame({
402
383
  {navigation === undefined ? null : (
403
384
  <DrawerNavigation>{navigation}</DrawerNavigation>
404
385
  )}
405
- <DrawerTitle>{title}</DrawerTitle>
406
386
  {headerActions === undefined &&
407
387
  assistantAction === undefined ? null : (
408
388
  <DrawerActions>
@@ -413,7 +393,14 @@ function PresentationFrame({
413
393
  </DrawerActions>
414
394
  )}
415
395
  </DrawerHeader>
416
- {withAssistant(<DrawerBody>{body}</DrawerBody>)}
396
+ {withAssistant(
397
+ <DrawerBody>
398
+ <DrawerIntro>
399
+ <DrawerTitle>{title}</DrawerTitle>
400
+ </DrawerIntro>
401
+ {body}
402
+ </DrawerBody>,
403
+ )}
417
404
  {hasFooter ? (
418
405
  <DrawerFooter>{renderFooterActions()}</DrawerFooter>
419
406
  ) : null}
@@ -635,9 +622,7 @@ export function Presentation({
635
622
  string,
636
623
  unknown
637
624
  >;
638
- // Header e footer são regiões de decisão da superfície, não toolbars operacionais.
639
- // Seus comandos textuais usam a linha padrão; listas e grids continuam donos da
640
- // densidade compacta de suas próprias ações.
625
+ // A região chamadora escolhe a escala; o footer conserva o botão padrão.
641
626
  if (action.kind === "simple") {
642
627
  return (
643
628
  <PresentationCommandTrigger
@@ -784,7 +769,7 @@ export function Presentation({
784
769
 
785
770
  const headerActions = definition.actions
786
771
  .filter((command) => command.placement === "header")
787
- .map((command) => renderCommand(command));
772
+ .map((command) => renderCommand(command, "sm"));
788
773
  const footerActions = definition.actions
789
774
  .filter((command) => command.placement === "footer")
790
775
  .map((command) => renderCommand(command));
@@ -794,7 +779,7 @@ export function Presentation({
794
779
  (invocation.surface === "page" || parentSurface !== "page");
795
780
  const navigation = !canGoBack ? undefined : (
796
781
  <Button
797
- size="icon"
782
+ size="icon-sm"
798
783
  variant="ghost"
799
784
  disabled={surfaceBlocked}
800
785
  aria-label="Voltar"
@@ -807,7 +792,7 @@ export function Presentation({
807
792
  const assistantAction =
808
793
  definition.assistant === undefined || assistantOpen ? undefined : (
809
794
  <Button
810
- size="icon"
795
+ size="icon-sm"
811
796
  variant="ghost"
812
797
  aria-label={definition.assistant.triggerLabel}
813
798
  title={definition.assistant.triggerLabel}