@beyondwork/docx-react-component 1.0.30 → 1.0.32

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 (38) hide show
  1. package/README.md +6 -0
  2. package/package.json +1 -1
  3. package/src/api/public-types.ts +16 -1
  4. package/src/api/session-state.ts +2 -0
  5. package/src/io/docx-session.ts +16 -3
  6. package/src/io/ooxml/parse-footnotes.ts +23 -33
  7. package/src/io/ooxml/parse-headers-footers.ts +20 -21
  8. package/src/io/ooxml/workflow-payload.ts +311 -8
  9. package/src/model/snapshot.ts +113 -1
  10. package/src/runtime/document-runtime.ts +207 -33
  11. package/src/runtime/surface-projection.ts +156 -7
  12. package/src/ui/WordReviewEditor.tsx +13 -5
  13. package/src/ui/editor-surface-controller.tsx +2 -0
  14. package/src/ui/headless/selection-tool-resolver.ts +4 -1
  15. package/src/ui/headless/selection-tool-types.ts +1 -2
  16. package/src/ui/workflow-surface-blocked-rails.ts +19 -1
  17. package/src/ui-tailwind/chrome/responsive-chrome.ts +46 -0
  18. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +4 -4
  19. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +5 -5
  20. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +3 -3
  21. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +4 -4
  22. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +14 -9
  23. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +4 -5
  24. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +5 -5
  25. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +6 -6
  26. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +9 -9
  27. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +173 -124
  28. package/src/ui-tailwind/editor-surface/pm-decorations.ts +88 -14
  29. package/src/ui-tailwind/editor-surface/pm-schema.ts +29 -0
  30. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +13 -1
  31. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +3 -3
  32. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +20 -0
  33. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +26 -0
  34. package/src/ui-tailwind/review/tw-review-rail.tsx +9 -1
  35. package/src/ui-tailwind/theme/editor-theme.css +8 -0
  36. package/src/ui-tailwind/toolbar/toolbar-layout.ts +47 -0
  37. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +367 -22
  38. package/src/ui-tailwind/tw-review-workspace.tsx +131 -4
@@ -3,7 +3,6 @@ import type {
3
3
  CommentSidebarThreadSnapshot,
4
4
  FormattingStateSnapshot,
5
5
  InteractionGuardSnapshot,
6
- StyleCatalogSnapshot,
7
6
  TableStructureContextSnapshot,
8
7
  SuggestionsSnapshot,
9
8
  TrackedChangeEntrySnapshot,
@@ -79,7 +78,7 @@ export type StructureContextKind = "table" | "image" | "object" | "list";
79
78
  export interface StructureContextSelectionToolModel extends BaseSelectionToolModel {
80
79
  kind: "structure-context";
81
80
  structureKind: StructureContextKind;
82
- tableStyles?: StyleCatalogSnapshot["tables"];
81
+ tableStyles?: Array<{ styleId: string; displayName: string }>;
83
82
  activeTable?: TableStructureContextSnapshot | null;
84
83
  activeImage?: ActiveImageContext;
85
84
  activeObject?: ActiveObjectContext;
@@ -3,6 +3,7 @@ import type {
3
3
  RuntimeRenderSnapshot,
4
4
  SurfaceBlockSnapshot,
5
5
  WorkflowBlockedCommandReason,
6
+ WorkflowLockedZone,
6
7
  WorkflowMarkupSnapshot,
7
8
  } from "../api/public-types";
8
9
 
@@ -10,6 +11,21 @@ export function deriveVisibleWorkflowBlockedRails(
10
11
  surface: RuntimeRenderSnapshot["surface"] | undefined,
11
12
  markupSnapshot: WorkflowMarkupSnapshot | null,
12
13
  ): WorkflowBlockedCommandReason[] {
14
+ return deriveVisibleWorkflowLockedZones(surface, markupSnapshot).map((zone) => ({
15
+ code: zone.code,
16
+ message: zone.detail,
17
+ fragmentId: zone.fragmentId,
18
+ label: zone.label,
19
+ detail: zone.detail,
20
+ anchor: zone.anchor,
21
+ storyTarget: zone.storyTarget,
22
+ }));
23
+ }
24
+
25
+ export function deriveVisibleWorkflowLockedZones(
26
+ surface: RuntimeRenderSnapshot["surface"] | undefined,
27
+ markupSnapshot: WorkflowMarkupSnapshot | null,
28
+ ): WorkflowLockedZone[] {
13
29
  if (!surface || !markupSnapshot) {
14
30
  return [];
15
31
  }
@@ -22,8 +38,10 @@ export function deriveVisibleWorkflowBlockedRails(
22
38
  ),
23
39
  )
24
40
  .map((fragment) => ({
41
+ fragmentId: fragment.fragmentId,
25
42
  code: fragment.blockedReasonCode,
26
- message: fragment.detail,
43
+ label: fragment.label,
44
+ detail: fragment.detail,
27
45
  anchor: fragment.anchor,
28
46
  storyTarget: fragment.storyTarget,
29
47
  }));
@@ -0,0 +1,46 @@
1
+ export const NARROW_CHROME_MAX_WIDTH = 1023;
2
+
3
+ export interface ResponsiveChromeStateInput {
4
+ viewportWidth?: number;
5
+ reviewRailAvailable: boolean;
6
+ reviewRailOpen: boolean;
7
+ }
8
+
9
+ export interface ResponsiveChromeState {
10
+ isNarrow: boolean;
11
+ showSidebarToggle: boolean;
12
+ showDockedReviewRail: boolean;
13
+ showDrawerReviewRail: boolean;
14
+ }
15
+
16
+ export function isNarrowChromeViewport(viewportWidth?: number): boolean {
17
+ return typeof viewportWidth === "number" && viewportWidth <= NARROW_CHROME_MAX_WIDTH;
18
+ }
19
+
20
+ export function getInitialReviewRailOpen(input: {
21
+ viewportWidth?: number;
22
+ reviewRailAvailable: boolean;
23
+ }): boolean {
24
+ return input.reviewRailAvailable && !isNarrowChromeViewport(input.viewportWidth);
25
+ }
26
+
27
+ export function resolveResponsiveChromeState(
28
+ input: ResponsiveChromeStateInput,
29
+ ): ResponsiveChromeState {
30
+ const isNarrow = isNarrowChromeViewport(input.viewportWidth);
31
+ if (!input.reviewRailAvailable) {
32
+ return {
33
+ isNarrow,
34
+ showSidebarToggle: false,
35
+ showDockedReviewRail: false,
36
+ showDrawerReviewRail: false,
37
+ };
38
+ }
39
+
40
+ return {
41
+ isNarrow,
42
+ showSidebarToggle: true,
43
+ showDockedReviewRail: !isNarrow && input.reviewRailOpen,
44
+ showDrawerReviewRail: isNarrow && input.reviewRailOpen,
45
+ };
46
+ }
@@ -30,12 +30,12 @@ export function TwImageContextToolbar(props: TwImageContextToolbarProps) {
30
30
  return (
31
31
  <div
32
32
  data-testid="image-context-toolbar"
33
- className="flex flex-wrap items-center gap-2 rounded-xl border border-border bg-canvas px-3 py-2 shadow-sm"
33
+ className="flex flex-wrap items-center gap-1.5 rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-sm"
34
34
  >
35
- <span className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
35
+ <span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
36
36
  Image
37
37
  </span>
38
- <span className="rounded-full bg-surface px-2 py-1 text-[10px] font-medium uppercase tracking-[0.1em] text-secondary">
38
+ <span className="rounded-full bg-surface px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-[0.1em] text-secondary">
39
39
  {activeImage.display}
40
40
  </span>
41
41
  {IMAGE_SIZE_PRESETS.map((preset) => (
@@ -113,7 +113,7 @@ function ToolbarButton(props: {
113
113
  disabled={props.disabled}
114
114
  onMouseDown={preserveEditorSelectionMouseDown}
115
115
  onClick={props.onClick}
116
- className="inline-flex h-8 items-center rounded-md px-2 text-xs font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
116
+ className="inline-flex h-7 items-center rounded-md px-2 text-[11px] font-medium text-primary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
117
117
  >
118
118
  {props.children}
119
119
  </button>
@@ -11,18 +11,18 @@ export function TwObjectContextToolbar(props: TwObjectContextToolbarProps) {
11
11
  return (
12
12
  <div
13
13
  data-testid="object-context-toolbar"
14
- className="flex flex-wrap items-center gap-2 rounded-xl border border-border bg-canvas px-3 py-2 shadow-sm"
14
+ className="flex flex-wrap items-center gap-1.5 rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-sm"
15
15
  >
16
- <span className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
16
+ <span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
17
17
  Object
18
18
  </span>
19
- <span className="rounded-full bg-surface px-2 py-1 text-[10px] font-medium uppercase tracking-[0.1em] text-secondary">
19
+ <span className="rounded-full bg-surface px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-[0.1em] text-secondary">
20
20
  {label}
21
21
  </span>
22
- <span className="rounded-full bg-surface px-2 py-1 text-[10px] font-medium uppercase tracking-[0.1em] text-secondary">
22
+ <span className="rounded-full bg-surface px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-[0.1em] text-secondary">
23
23
  {props.activeObject.display}
24
24
  </span>
25
- <span className="text-xs text-secondary">
25
+ <span className="text-[11px] text-secondary">
26
26
  Object selection is active.
27
27
  </span>
28
28
  </div>
@@ -10,14 +10,14 @@ export function TwSelectionToolBlocked(props: TwSelectionToolBlockedProps) {
10
10
  return (
11
11
  <div
12
12
  data-testid="blocked-selection-tool"
13
- className="max-w-[min(24rem,calc(100vw-2rem))] rounded-xl border border-border bg-canvas px-3 py-2 shadow-lg"
13
+ className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-md"
14
14
  role="status"
15
15
  aria-live="polite"
16
16
  >
17
- <div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
17
+ <div className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
18
18
  Unavailable here
19
19
  </div>
20
- <div className="mt-1 text-sm text-primary">{props.model.disabledReason}</div>
20
+ <div className="mt-0.5 text-[13px] text-primary">{props.model.disabledReason}</div>
21
21
  </div>
22
22
  );
23
23
  }
@@ -12,12 +12,12 @@ export function TwSelectionToolComment(props: TwSelectionToolCommentProps) {
12
12
  return (
13
13
  <div
14
14
  data-testid="comment-thread-selection-tool"
15
- className="max-w-[min(24rem,calc(100vw-2rem))] rounded-xl border border-border bg-canvas px-3 py-2 shadow-lg"
15
+ className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-md"
16
16
  >
17
- <div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
17
+ <div className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
18
18
  Comment
19
19
  </div>
20
- <div className="mt-1 truncate text-sm text-primary">
20
+ <div className="mt-0.5 truncate text-[13px] text-primary">
21
21
  {props.model.previewText ?? "Comment thread"}
22
22
  </div>
23
23
  <button
@@ -26,7 +26,7 @@ export function TwSelectionToolComment(props: TwSelectionToolCommentProps) {
26
26
  disabled={!props.model.canAddComment || !props.onAddComment}
27
27
  onMouseDown={preserveEditorSelectionMouseDown}
28
28
  onClick={props.onAddComment}
29
- className="mt-2 inline-flex h-8 items-center rounded-lg border border-border px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
29
+ className="mt-1.5 inline-flex h-7 items-center rounded-md border border-border px-2 text-[11px] font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
30
30
  >
31
31
  Comment
32
32
  </button>
@@ -112,7 +112,7 @@ export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
112
112
  ref={props.rootRef}
113
113
  onFocusCapture={props.onFocusCapture}
114
114
  onBlurCapture={props.onBlurCapture}
115
- className="flex flex-col gap-2"
115
+ className="flex flex-col gap-1.5"
116
116
  >
117
117
  {props.contextAnalytics ? (
118
118
  <TwContextAnalyticsSummary
@@ -125,13 +125,13 @@ export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
125
125
  </div>
126
126
  ) : null;
127
127
  const wrappedContent = content && supportsTopMenuControls ? (
128
- <div className="flex flex-col gap-1.5">
129
- <div className="inline-flex items-center gap-2 self-center rounded-xl border border-border/70 bg-canvas/94 px-2 py-1.5 shadow-[0_8px_20px_-18px_var(--color-shadow-strong)]">
128
+ <div className="flex flex-col gap-1">
129
+ <div className="inline-flex items-center gap-1.5 self-center rounded-lg border border-border/70 bg-canvas/94 px-1.5 py-1 shadow-[0_8px_20px_-18px_var(--color-shadow-strong)]">
130
130
  <button
131
131
  type="button"
132
- aria-label="Drag top menu"
132
+ aria-label={isDetached ? "Drag floating menu" : "Drag to float menu"}
133
133
  data-testid="selection-tool-drag-handle"
134
- className="inline-flex h-7 items-center justify-center rounded-lg border border-transparent px-2 text-tertiary transition-colors hover:border-border/60 hover:bg-surface hover:text-primary"
134
+ className="inline-flex h-6 items-center justify-center rounded-md border border-transparent px-1.5 text-tertiary transition-colors hover:border-border/60 hover:bg-surface hover:text-primary"
135
135
  onMouseDown={(event) => {
136
136
  preserveEditorSelectionMouseDown(event);
137
137
  setIsDetached(true);
@@ -143,21 +143,26 @@ export function TwSelectionToolHost(props: TwSelectionToolHostProps) {
143
143
  };
144
144
  }}
145
145
  >
146
- <GripHorizontal className="h-3.5 w-3.5" />
146
+ <GripHorizontal className="h-3 w-3" />
147
147
  </button>
148
- <span className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
148
+ <span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
149
149
  {getTopMenuLabel(props.tool.kind)}
150
150
  </span>
151
+ <span className="rounded-full bg-surface px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-[0.08em] text-secondary">
152
+ {isDetached ? "Floating" : "Docked"}
153
+ </span>
151
154
  <button
152
155
  type="button"
156
+ aria-label={isDetached ? "Dock menu" : "Float menu"}
157
+ aria-pressed={isDetached}
153
158
  data-testid="selection-tool-attach-toggle"
154
- className="inline-flex h-7 items-center rounded-lg border border-border/60 px-2.5 text-[11px] font-medium text-secondary transition-colors hover:bg-surface hover:text-primary"
159
+ className="inline-flex h-6 items-center rounded-md border border-border/60 px-2 text-[10px] font-medium text-secondary transition-colors hover:bg-surface hover:text-primary"
155
160
  onMouseDown={preserveEditorSelectionMouseDown}
156
161
  onClick={() => {
157
162
  setIsDetached((current) => !current);
158
163
  }}
159
164
  >
160
- {isDetached ? "Attach menu" : "Detach menu"}
165
+ {isDetached ? "Dock menu" : "Float menu"}
161
166
  </button>
162
167
  </div>
163
168
  {content}
@@ -52,8 +52,7 @@ export function TwSelectionToolStructure(props: TwSelectionToolStructureProps) {
52
52
  return (
53
53
  <TwTableContextToolbar
54
54
  disabled={!props.model.canMutate}
55
- tableContext={props.model.activeTable ?? null}
56
- tableStyles={props.model.tableStyles ?? []}
55
+ tableContext={props.model.activeTable ?? null} tableStyles={props.model.tableStyles ?? []}
57
56
  onSetTableStyle={props.onSetTableStyle}
58
57
  onAddRowBefore={props.onAddRowBefore}
59
58
  onAddRowAfter={props.onAddRowAfter}
@@ -71,9 +70,9 @@ export function TwSelectionToolStructure(props: TwSelectionToolStructureProps) {
71
70
  return (
72
71
  <div
73
72
  data-testid="list-context-toolbar"
74
- className="flex flex-wrap items-center gap-2 rounded-xl border border-border bg-canvas px-3 py-2 shadow-sm"
73
+ className="flex flex-wrap items-center gap-1.5 rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-sm"
75
74
  >
76
- <span className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
75
+ <span className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
77
76
  List
78
77
  </span>
79
78
  <ToolbarButton
@@ -108,7 +107,7 @@ function ToolbarButton(props: {
108
107
  disabled={props.disabled}
109
108
  onMouseDown={preserveEditorSelectionMouseDown}
110
109
  onClick={props.onClick}
111
- className="inline-flex h-8 items-center rounded-lg border border-border px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
110
+ className="inline-flex h-7 items-center rounded-md border border-border px-2 text-[11px] font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40"
112
111
  >
113
112
  {props.children}
114
113
  </button>
@@ -10,17 +10,17 @@ export function TwSelectionToolWorkflow(props: TwSelectionToolWorkflowProps) {
10
10
  return (
11
11
  <div
12
12
  data-testid="workflow-task-selection-tool"
13
- className="max-w-[min(24rem,calc(100vw-2rem))] rounded-xl border border-border bg-canvas px-3 py-2 shadow-lg"
13
+ className="max-w-[min(20rem,calc(100vw-1.5rem))] rounded-lg border border-border bg-canvas px-2.5 py-1.5 shadow-md"
14
14
  >
15
- <div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-tertiary">
15
+ <div className="text-[9px] font-semibold uppercase tracking-[0.12em] text-tertiary">
16
16
  Workflow task
17
17
  </div>
18
- <div className="mt-1 text-sm text-primary">{props.model.workflowTitle ?? "Scoped task"}</div>
18
+ <div className="mt-0.5 text-[13px] text-primary">{props.model.workflowTitle ?? "Scoped task"}</div>
19
19
  {props.model.workflowDetail ? (
20
- <div className="mt-1 text-xs text-secondary">{props.model.workflowDetail}</div>
20
+ <div className="mt-0.5 text-[11px] text-secondary">{props.model.workflowDetail}</div>
21
21
  ) : null}
22
22
  {props.model.disabledReason ? (
23
- <div className="mt-2 text-xs text-tertiary">{props.model.disabledReason}</div>
23
+ <div className="mt-1 text-[11px] text-tertiary">{props.model.disabledReason}</div>
24
24
  ) : null}
25
25
  </div>
26
26
  );
@@ -35,7 +35,7 @@ export const TwSelectionToolbar = forwardRef<HTMLDivElement, TwSelectionToolbarP
35
35
  <div
36
36
  ref={ref}
37
37
  data-testid="selection-toolbar"
38
- className="inline-flex max-w-[min(24rem,calc(100vw-2rem))] items-center gap-1.5 rounded-xl border border-border/80 bg-canvas px-1.5 py-1.5 shadow-lg ring-1 ring-border/80"
38
+ className="inline-flex max-w-[min(22rem,calc(100vw-1.5rem))] items-center gap-1 rounded-lg border border-border/80 bg-canvas px-1 py-1 shadow-md ring-1 ring-border/70"
39
39
  role="toolbar"
40
40
  aria-label="Selection actions"
41
41
  onFocusCapture={props.onFocusCapture}
@@ -87,9 +87,9 @@ export const TwSelectionToolbar = forwardRef<HTMLDivElement, TwSelectionToolbarP
87
87
  disabled={addCommentDisabled}
88
88
  onMouseDown={preserveEditorSelectionMouseDown}
89
89
  onClick={props.onAddComment}
90
- className={`inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors text-accent hover:bg-surface disabled:cursor-not-allowed disabled:opacity-30 ${focusRingClass}`}
90
+ className={`inline-flex h-6 w-6 items-center justify-center rounded-md transition-colors text-accent hover:bg-surface disabled:cursor-not-allowed disabled:opacity-30 ${focusRingClass}`}
91
91
  >
92
- <MessageSquare className="h-3.5 w-3.5" />
92
+ <MessageSquare className="h-3 w-3" />
93
93
  </button>
94
94
  </Tooltip.Trigger>
95
95
  <Tooltip.Portal>
@@ -105,7 +105,7 @@ export const TwSelectionToolbar = forwardRef<HTMLDivElement, TwSelectionToolbarP
105
105
  {model.previewText ? (
106
106
  <>
107
107
  <div className="mx-0.5 h-4 w-px bg-border" />
108
- <span className="max-w-[8rem] truncate text-[11px] text-secondary">
108
+ <span className="max-w-[7rem] truncate text-[10px] text-secondary">
109
109
  {model.previewText}
110
110
  </span>
111
111
  </>
@@ -115,7 +115,7 @@ export const TwSelectionToolbar = forwardRef<HTMLDivElement, TwSelectionToolbarP
115
115
  <>
116
116
  {!model.previewText ? <div className="mx-0.5 h-4 w-px bg-border" /> : null}
117
117
  <span
118
- className={`min-w-0 max-w-[11rem] truncate rounded-full px-2 py-0.5 text-[10px] font-medium tracking-[0.08em] ${
118
+ className={`min-w-0 max-w-[9rem] truncate rounded-full px-1.5 py-0.5 text-[9px] font-medium tracking-[0.08em] ${
119
119
  model.badges.some((badge) => badge.tone === "accent")
120
120
  ? "bg-canvas text-accent ring-1 ring-accent/25"
121
121
  : "bg-surface text-tertiary"
@@ -164,7 +164,7 @@ function ToolbarActionButton(props: ToolbarActionButtonProps) {
164
164
  disabled={props.disabled}
165
165
  onMouseDown={preserveEditorSelectionMouseDown}
166
166
  onClick={props.onClick}
167
- className={`inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors disabled:cursor-not-allowed disabled:opacity-30 ${
167
+ className={`inline-flex h-6 w-6 items-center justify-center rounded-md transition-colors disabled:cursor-not-allowed disabled:opacity-30 ${
168
168
  props.pressed
169
169
  ? "bg-canvas text-accent ring-1 ring-accent/30 shadow-sm"
170
170
  : "text-secondary hover:bg-surface"
@@ -29,29 +29,29 @@ export function TwSuggestionCard(props: TwSuggestionCardProps) {
29
29
  return (
30
30
  <div
31
31
  data-testid="suggestion-card"
32
- className="inline-flex max-w-[min(28rem,calc(100vw-2rem))] flex-col gap-2 rounded-2xl border border-border/80 bg-canvas px-3 py-2 shadow-xl ring-1 ring-border/80"
32
+ className="inline-flex max-w-[min(24rem,calc(100vw-1.5rem))] flex-col gap-1.5 rounded-xl border border-border/80 bg-canvas px-2.5 py-1.5 shadow-lg ring-1 ring-border/75"
33
33
  onFocusCapture={props.onFocusCapture}
34
34
  onBlurCapture={props.onBlurCapture}
35
35
  role="group"
36
36
  aria-label="Suggestion actions"
37
37
  >
38
- <div className="flex items-start justify-between gap-3">
38
+ <div className="flex items-start justify-between gap-2">
39
39
  <div className="min-w-0">
40
- <div className="text-[11px] font-semibold uppercase tracking-[0.12em] text-warning">
40
+ <div className="text-[10px] font-semibold uppercase tracking-[0.12em] text-warning">
41
41
  {props.model.kindLabel}
42
42
  </div>
43
- <div className="mt-1 max-w-[16rem] truncate text-sm text-primary">
43
+ <div className="mt-0.5 max-w-[14rem] truncate text-[13px] text-primary">
44
44
  {props.model.previewText}
45
45
  </div>
46
46
  </div>
47
47
  {contextLabel ? (
48
- <div className="shrink-0 rounded-full bg-canvas px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.08em] text-comment ring-1 ring-comment/20">
48
+ <div className="shrink-0 rounded-full bg-canvas px-1.5 py-0.5 text-[9px] font-medium uppercase tracking-[0.08em] text-comment ring-1 ring-comment/20">
49
49
  {contextLabel}
50
50
  </div>
51
51
  ) : null}
52
52
  </div>
53
53
 
54
- <div className="flex flex-wrap items-center gap-1.5">
54
+ <div className="flex flex-wrap items-center gap-1">
55
55
  <SuggestionActionButton
56
56
  icon={<Check className="h-3.5 w-3.5" />}
57
57
  label="Accept suggestion"
@@ -81,9 +81,9 @@ export function TwSuggestionCard(props: TwSuggestionCardProps) {
81
81
  disabled={commentDisabled}
82
82
  onMouseDown={preserveEditorSelectionMouseDown}
83
83
  onClick={props.onAddComment}
84
- className={`inline-flex h-8 items-center gap-1 rounded-lg border border-border px-2.5 text-xs font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
84
+ className={`inline-flex h-7 items-center gap-1 rounded-md border border-border px-2 text-[11px] font-medium text-secondary transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-40 ${focusRingClass}`}
85
85
  >
86
- <MessageSquare className="h-3.5 w-3.5" />
86
+ <MessageSquare className="h-3 w-3" />
87
87
  Comment
88
88
  </button>
89
89
  </Tooltip.Trigger>
@@ -130,7 +130,7 @@ function SuggestionActionButton(props: {
130
130
  disabled={props.disabled}
131
131
  onMouseDown={preserveEditorSelectionMouseDown}
132
132
  onClick={props.onClick}
133
- className={`inline-flex h-8 items-center gap-1 rounded-lg border px-2.5 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${toneClass} ${focusRingClass}`}
133
+ className={`inline-flex h-7 items-center gap-1 rounded-md border px-2 text-[11px] font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${toneClass} ${focusRingClass}`}
134
134
  >
135
135
  {props.icon}
136
136
  {props.label.replace(" suggestion", "").replace(" on suggestion", "")}