create-principles-disciple 1.145.35 → 1.145.37

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 (81) hide show
  1. package/_release/product-identity.json +2 -2
  2. package/console/dist/server/index.js +4 -0
  3. package/console/dist/server/models/ApprovalsConsoleModel.d.ts +22 -2
  4. package/console/dist/server/models/ApprovalsConsoleModel.js +56 -16
  5. package/console/dist/server/models/ApprovalsGroupedConsoleModel.d.ts +8 -0
  6. package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +3 -2
  7. package/console/dist/server/models/approval-review-material.d.ts +15 -0
  8. package/console/dist/server/models/approval-review-material.js +32 -0
  9. package/console/dist/server/routes/approvals.js +32 -7
  10. package/console/dist/server/utils/response.js +8 -0
  11. package/console/dist/ui/api.d.ts +8 -2
  12. package/console/dist/ui/api.js +8 -4
  13. package/console/dist/ui/i18n/en.json +98 -7
  14. package/console/dist/ui/i18n/zh-CN.json +98 -7
  15. package/console/dist/ui/pages/focus/ApprovalSubjectReading.d.ts +24 -0
  16. package/console/dist/ui/pages/focus/ApprovalSubjectReading.js +133 -0
  17. package/console/dist/ui/pages/focus/FocusPage.d.ts +5 -26
  18. package/console/dist/ui/pages/focus/FocusPage.js +166 -459
  19. package/console/dist/ui/pages/focus/OwnerWorkbench.d.ts +19 -0
  20. package/console/dist/ui/pages/focus/OwnerWorkbench.js +75 -0
  21. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.d.ts +4 -1
  22. package/console/dist/ui/pages/focus/RuleCandidateBindingPanel.js +7 -3
  23. package/console/dist/ui/pages/focus/focus-validation.js +31 -0
  24. package/console/dist/ui/pages/principles/PrincipleDetailPage.js +110 -86
  25. package/console/dist/ui/pages/principles/PrinciplesPage.js +39 -30
  26. package/console/dist/ui/utils/approval-review.d.ts +30 -0
  27. package/console/dist/ui/utils/approval-review.js +50 -0
  28. package/console/dist/ui/utils/reading-layout.d.ts +1 -0
  29. package/console/dist/ui/utils/reading-layout.js +1 -1
  30. package/console/dist/ui/utils/validators.d.ts +4 -0
  31. package/console/dist/ui/utils/validators.js +10 -2
  32. package/console/dist/web/assets/app.css +119 -11
  33. package/console/dist/web/assets/app.js +40 -30
  34. package/core/dist/runtime-v2/activation/activation-types.d.ts +12 -2
  35. package/core/dist/runtime-v2/activation/activation-types.d.ts.map +1 -1
  36. package/core/dist/runtime-v2/activation/activation-types.js.map +1 -1
  37. package/core/dist/runtime-v2/activation/approval-queue.d.ts +3 -2
  38. package/core/dist/runtime-v2/activation/approval-queue.d.ts.map +1 -1
  39. package/core/dist/runtime-v2/activation/approval-queue.js +6 -4
  40. package/core/dist/runtime-v2/activation/approval-queue.js.map +1 -1
  41. package/core/dist/runtime-v2/activation/approval-review-guard.d.ts +26 -0
  42. package/core/dist/runtime-v2/activation/approval-review-guard.d.ts.map +1 -0
  43. package/core/dist/runtime-v2/activation/approval-review-guard.js +25 -0
  44. package/core/dist/runtime-v2/activation/approval-review-guard.js.map +1 -0
  45. package/core/dist/runtime-v2/activation/memory-approval-store.d.ts +6 -2
  46. package/core/dist/runtime-v2/activation/memory-approval-store.d.ts.map +1 -1
  47. package/core/dist/runtime-v2/activation/memory-approval-store.js +26 -2
  48. package/core/dist/runtime-v2/activation/memory-approval-store.js.map +1 -1
  49. package/core/dist/runtime-v2/activation/sqlite-approval-store.d.ts +6 -2
  50. package/core/dist/runtime-v2/activation/sqlite-approval-store.d.ts.map +1 -1
  51. package/core/dist/runtime-v2/activation/sqlite-approval-store.js +59 -2
  52. package/core/dist/runtime-v2/activation/sqlite-approval-store.js.map +1 -1
  53. package/core/dist/runtime-v2/diagnostician-association-shadow.d.ts +24 -0
  54. package/core/dist/runtime-v2/diagnostician-association-shadow.d.ts.map +1 -0
  55. package/core/dist/runtime-v2/diagnostician-association-shadow.js +84 -0
  56. package/core/dist/runtime-v2/diagnostician-association-shadow.js.map +1 -0
  57. package/core/dist/runtime-v2/diagnostician-association-validation.d.ts +18 -0
  58. package/core/dist/runtime-v2/diagnostician-association-validation.d.ts.map +1 -0
  59. package/core/dist/runtime-v2/diagnostician-association-validation.js +47 -0
  60. package/core/dist/runtime-v2/diagnostician-association-validation.js.map +1 -0
  61. package/core/dist/runtime-v2/diagnostician-output.d.ts +123 -0
  62. package/core/dist/runtime-v2/diagnostician-output.d.ts.map +1 -1
  63. package/core/dist/runtime-v2/diagnostician-output.js +41 -0
  64. package/core/dist/runtime-v2/diagnostician-output.js.map +1 -1
  65. package/core/dist/runtime-v2/index.d.ts +2 -0
  66. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  67. package/core/dist/runtime-v2/index.js +1 -0
  68. package/core/dist/runtime-v2/index.js.map +1 -1
  69. package/core/dist/runtime-v2/internalization/diag-router-runner.d.ts +12 -1
  70. package/core/dist/runtime-v2/internalization/diag-router-runner.d.ts.map +1 -1
  71. package/core/dist/runtime-v2/internalization/diag-router-runner.js +50 -2
  72. package/core/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
  73. package/core/dist/runtime-v2/internalization/router-context-evidence.d.ts +45 -0
  74. package/core/dist/runtime-v2/internalization/router-context-evidence.d.ts.map +1 -0
  75. package/core/dist/runtime-v2/internalization/router-context-evidence.js +64 -0
  76. package/core/dist/runtime-v2/internalization/router-context-evidence.js.map +1 -0
  77. package/core/package.json +1 -1
  78. package/package.json +1 -1
  79. package/pd-cli/package.json +1 -1
  80. package/plugin/dist/bundle.js +395 -395
  81. package/release-manager/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState, useEffect, useCallback } from "react";
3
- import { useNavigate } from "react-router-dom";
2
+ import { useState, useEffect, useCallback, useRef } from "react";
3
+ import { Link, useSearchParams } from "react-router-dom";
4
4
  import { useTranslation } from "react-i18next";
5
5
  import { PageShell } from "../../components/layout/page-shell.js";
6
6
  import { SectionTitle } from "../../components/layout/section-title.js";
@@ -9,6 +9,7 @@ import { Skeleton } from "../../components/ui/skeleton.js";
9
9
  import { ShinyText } from "../../components/ui/shiny-text.js";
10
10
  import { fetchPrinciples, fetchApprovalsGrouped, fetchOwnerDecisionLibrary, } from "../../api.js";
11
11
  import { enumLabel } from "../../utils/enum-labels.js";
12
+ import { splitReadingTitleBody, truncateByCodePoints } from "../../utils/reading-layout.js";
12
13
  import { formatDate } from "../../utils/format-date.js";
13
14
  import { PrinciplesViewNav } from "./PrinciplesViewNav.js";
14
15
  // ── Map principle status to review status ───────────────────────────────────
@@ -140,7 +141,6 @@ function validateApprovalsGrouped(data) {
140
141
  // ── Component ───────────────────────────────────────────────────────────────
141
142
  export function PrinciplesPage() {
142
143
  const { t, i18n } = useTranslation("pages");
143
- const navigate = useNavigate();
144
144
  const [principles, setPrinciples] = useState([]);
145
145
  const [approvalGroups, setApprovalGroups] = useState([]);
146
146
  const [loading, setLoading] = useState(true);
@@ -148,20 +148,29 @@ export function PrinciplesPage() {
148
148
  // PRI-558: summary powers the metrics bar (already returned by /api/principles)
149
149
  const [summary, setSummary] = useState(null);
150
150
  // Search / filter / sort
151
- const [search, setSearch] = useState("");
152
- const [statusFilter, setStatusFilter] = useState("all");
153
- const [sortBy, setSortBy] = useState("updatedAt");
151
+ const [searchParams, setSearchParams] = useSearchParams();
152
+ const updateQuery = (key, value) => { const next = new URLSearchParams(searchParams); next.set(key, value); setSearchParams(next, { replace: true, preventScrollReset: true }); };
153
+ const search = searchParams.get('q') ?? '';
154
+ const setSearch = (value) => updateQuery('q', value);
155
+ const statusParam = searchParams.get('status');
156
+ const statusFilter = statusParam === 'pending' || statusParam === 'candidate' || statusParam === 'approved' || statusParam === 'rejected' || statusParam === 'parked' ? statusParam : 'all';
157
+ const setStatusFilter = (value) => updateQuery('status', value);
158
+ const sortBy = searchParams.get('sort') === 'createdAt' ? 'createdAt' : 'updatedAt';
159
+ const setSortBy = (value) => updateQuery('sort', value);
154
160
  // PRI-629 (INV-02): 默认展示全部 — candidate 是生命周期而非待办,
155
161
  // "只看待决策"不再作为默认视图 (真实决策在治理焦点)。
156
- const [filterMode, setFilterMode] = useState('all');
162
+ const filterMode = searchParams.get('mode') === 'actionable' ? 'actionable' : 'all';
163
+ const setFilterMode = (value) => updateQuery('mode', value);
157
164
  const [categories, setCategories] = useState();
158
165
  // Owner Decision Experience v1: backend semantic titles + decision states
159
166
  // (display-only — the page never re-derives decision qualification).
160
167
  const [ownerDecisionLibrary, setOwnerDecisionLibrary] = useState(null);
161
168
  // PRI-956 (F15, D3): runtime status cannot be confirmed when set
162
169
  const [revocationUnavailable, setRevocationUnavailable] = useState(null);
170
+ const requestEpoch = useRef(0);
163
171
  // Fetch data
164
172
  const loadData = useCallback(async () => {
173
+ const epoch = ++requestEpoch.current;
165
174
  setLoading(true);
166
175
  setError(null);
167
176
  try {
@@ -170,6 +179,8 @@ export function PrinciplesPage() {
170
179
  fetchApprovalsGrouped(),
171
180
  fetchOwnerDecisionLibrary(),
172
181
  ]);
182
+ if (epoch !== requestEpoch.current)
183
+ return;
173
184
  setOwnerDecisionLibrary(dResult.success && dResult.data ? dResult.data : null);
174
185
  if (!pResult.success) {
175
186
  setError(pResult.error ?? "Failed to load principles");
@@ -191,14 +202,18 @@ export function PrinciplesPage() {
191
202
  setApprovalGroups(aData?.groups ?? []);
192
203
  }
193
204
  catch (err) {
205
+ if (epoch !== requestEpoch.current)
206
+ return;
194
207
  setError(err instanceof Error ? err.message : "Unknown error");
195
208
  }
196
209
  finally {
197
- setLoading(false);
210
+ if (epoch === requestEpoch.current)
211
+ setLoading(false);
198
212
  }
199
213
  }, [filterMode]);
200
214
  useEffect(() => {
201
- loadData();
215
+ void loadData();
216
+ return () => { requestEpoch.current++; };
202
217
  }, [loadData]);
203
218
  // Build approval lookup
204
219
  const approvalByPrinciple = new Map();
@@ -219,7 +234,7 @@ export function PrinciplesPage() {
219
234
  const decision = ownerDecisionLibrary?.entries[p.id];
220
235
  const semanticTitle = decision?.learnedPrinciple.status === 'known' ? decision.learnedPrinciple.text : undefined;
221
236
  // PRI-332: Determine display title with bounded fallback for unreadable titles
222
- const rawTitle = p.triggerPattern || p.text.slice(0, 80);
237
+ const rawTitle = p.triggerPattern || truncateByCodePoints(p.text, 80);
223
238
  const detectedLang = p.detectedLanguage ?? 'unknown';
224
239
  const readWarningCode = p.readabilityWarningCode;
225
240
  let displayTitle = rawTitle;
@@ -236,7 +251,8 @@ export function PrinciplesPage() {
236
251
  }
237
252
  return {
238
253
  principleId: p.id,
239
- title: displayTitle,
254
+ title: splitReadingTitleBody(displayTitle).title,
255
+ searchText: semanticTitle ?? rawTitle,
240
256
  originalTitle: rawTitle,
241
257
  titleUsedFallback: usedFallback,
242
258
  text: p.text,
@@ -260,7 +276,7 @@ export function PrinciplesPage() {
260
276
  return false;
261
277
  if (search) {
262
278
  const q = search.toLowerCase();
263
- if (!c.text.toLowerCase().includes(q) && !c.title.toLowerCase().includes(q))
279
+ if (!c.text.toLowerCase().includes(q) && !c.title.toLowerCase().includes(q) && !c.searchText.toLowerCase().includes(q))
264
280
  return false;
265
281
  }
266
282
  return true;
@@ -287,32 +303,25 @@ export function PrinciplesPage() {
287
303
  : "bg-surface text-ink-3 border-line hover:border-line-2"), children: [t("principles.filterAll", { defaultValue: "Show All" }), categories ? ` (${Object.values(categories).reduce((a, b) => a + b, 0)})` : ''] })] }), _jsx("input", { type: "text", value: search, onChange: (e) => setSearch(e.target.value), placeholder: t("principles.searchPlaceholder"), className: "flex-1 min-w-[200px] border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("principles.searchPlaceholder") }), _jsx("div", { className: "flex gap-1", role: "group", "aria-label": "Status filter", children: statusFilters.map((sf) => (_jsx("button", { onClick: () => setStatusFilter(sf.value), className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-1 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov " +
288
304
  (statusFilter === sf.value
289
305
  ? "bg-gov text-paper border-gov"
290
- : "bg-surface text-ink-3 border-line hover:border-line-2"), children: sf.label }, sf.value))) }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "border border-line rounded-[var(--radius-md)] bg-surface text-ink-3 px-2 py-1 text-[12px] font-mono focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": "Sort order", children: [_jsx("option", { value: "updatedAt", children: t("principles.sortByUpdated") }), _jsx("option", { value: "createdAt", children: t("principles.sortByCreated") })] })] }), !loading && !error && summary && (_jsxs("div", { className: "flex flex-wrap border border-line rounded-[var(--radius-md)] bg-panel overflow-hidden mb-6", children: [_jsx(Metric, { num: String(summary.total), label: t("principles.metricDeposited") }), _jsx(Metric, { num: String(summary.active), label: t("principles.metricActive") }), _jsx(Metric, { num: latestUpdatedAt ? formatDate(latestUpdatedAt, i18n.language) : "—", label: t("principles.metricLatest") })] })), loading && (_jsx("div", { className: "grid gap-3 animate-[pdFadeIn_200ms_ease-out]", role: "status", "aria-live": "polite", "aria-label": t("principles.loading", { defaultValue: "Loading…" }), children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "bg-panel border border-line rounded-[var(--radius-md)] p-4 border-l-[3px] border-l-transparent", children: [_jsxs("div", { className: "flex items-start justify-between gap-3 mb-3", children: [_jsx(Skeleton, { className: "h-5 w-[55%] rounded-sm" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-[2px]" })] }), _jsx(Skeleton, { className: "h-4 w-full rounded-sm mb-2" }), _jsx(Skeleton, { className: "h-4 w-[80%] rounded-sm mb-4" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-20 rounded-sm" }), _jsx(Skeleton, { className: "h-3.5 w-24 rounded-sm" })] })] }, i))) })), error && (_jsxs("div", { className: "border border-danger/30 rounded-[var(--radius-md)] p-4 mb-4", children: [_jsx("p", { className: "text-danger text-sm", children: error }), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, className: "mt-2", children: t("principles.retry", { defaultValue: "Retry" }) })] })), !loading && !error && filtered.length === 0 && principles.length === 0 && (_jsxs("div", { className: "text-center py-12", children: [_jsx("h2", { className: "text-ink-2 text-lg font-semibold mb-2", children: t("principles.emptyTitle") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[480px] mx-auto leading-relaxed", children: filterMode === 'actionable'
306
+ : "bg-surface text-ink-3 border-line hover:border-line-2"), children: sf.label }, sf.value))) }), _jsxs("select", { value: sortBy, onChange: (e) => setSortBy(e.target.value), className: "border border-line rounded-[var(--radius-md)] bg-surface text-ink-3 px-2 py-1 text-[12px] font-mono focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": "Sort order", children: [_jsx("option", { value: "updatedAt", children: t("principles.sortByUpdated") }), _jsx("option", { value: "createdAt", children: t("principles.sortByCreated") })] })] }), !loading && !error && summary && (_jsxs("details", { className: "mb-6 text-[12px] text-ink-3", "data-testid": "principle-library-overview", children: [_jsx("summary", { className: "cursor-pointer text-gov", children: t('principles.libraryOverview') }), _jsxs("div", { className: "mt-3 flex flex-wrap border border-line rounded-[var(--radius-md)] bg-panel overflow-hidden", children: [_jsx(Metric, { num: String(summary.total), label: t("principles.metricDeposited") }), _jsx(Metric, { num: String(summary.active), label: t("principles.metricActive") }), _jsx(Metric, { num: latestUpdatedAt ? formatDate(latestUpdatedAt, i18n.language) : "—", label: t("principles.metricLatest") })] })] })), loading && (_jsx("div", { className: "grid gap-3 animate-[pdFadeIn_200ms_ease-out]", role: "status", "aria-live": "polite", "aria-label": t("principles.loading", { defaultValue: "Loading…" }), children: Array.from({ length: 4 }).map((_, i) => (_jsxs("div", { className: "bg-panel border border-line rounded-[var(--radius-md)] p-4 border-l-[3px] border-l-transparent", children: [_jsxs("div", { className: "flex items-start justify-between gap-3 mb-3", children: [_jsx(Skeleton, { className: "h-5 w-[55%] rounded-sm" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-[2px]" })] }), _jsx(Skeleton, { className: "h-4 w-full rounded-sm mb-2" }), _jsx(Skeleton, { className: "h-4 w-[80%] rounded-sm mb-4" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-3.5 w-20 rounded-sm" }), _jsx(Skeleton, { className: "h-3.5 w-24 rounded-sm" })] })] }, i))) })), error && (_jsxs("div", { className: "border border-danger/30 rounded-[var(--radius-md)] p-4 mb-4", children: [_jsx("p", { className: "text-danger text-sm", children: error }), _jsx(Button, { variant: "outline", size: "sm", onClick: loadData, className: "mt-2", children: t("principles.retry", { defaultValue: "Retry" }) })] })), !loading && !error && filtered.length === 0 && principles.length === 0 && (_jsxs("div", { className: "text-center py-12", children: [_jsx("h2", { className: "text-ink-2 text-lg font-semibold mb-2", children: t("principles.emptyTitle") }), _jsx("p", { className: "text-ink-3 text-[14px] max-w-[480px] mx-auto leading-relaxed", children: filterMode === 'actionable'
291
307
  ? t("principles.emptyActionable", { defaultValue: "No owner-actionable principles at this time. When PD captures behavior deviation signals, principle candidates will appear here for your review." })
292
308
  : t("principles.emptyDescription") }), categories && filterMode === 'actionable' && (_jsx("div", { className: "flex justify-center gap-2 flex-wrap mt-4", children: Object.entries(categories).map(([cat, count]) => (_jsxs("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: [cat, ": ", count] }, cat))) }))] })), !loading && !error && filtered.length === 0 && principles.length > 0 && (_jsx("p", { className: "text-ink-3 text-sm", children: t("principles.noResults") })), !loading && !error && filtered.length > 0 && (_jsxs("div", { className: "relative pl-6 grid gap-3 animate-[pdFadeIn_400ms_ease-out]", children: [_jsx("span", { className: "absolute left-[12.5px] top-3 bottom-3 w-px bg-line", "aria-hidden": "true" }), filtered.map((card) => {
293
309
  const ag = approvalByPrinciple.get(card.principleId);
294
310
  const decision = GOV_DECISION[card.status];
295
311
  const decisionRecord = ag?.records?.find((r) => r.status === ag.status) ?? ag?.records?.[0];
296
312
  const decisionDate = decisionRecord?.createdAt;
297
- return (_jsxs("article", { onClick: () => navigate(`/principles/${card.principleId}`), className: "relative bg-panel border border-line rounded-[var(--radius-md)] p-4 cursor-pointer " +
313
+ return (_jsxs("article", { className: "relative bg-panel border border-line rounded-[var(--radius-md)] p-5 " +
298
314
  "border-l-[3px] " +
299
315
  STATUS_BORDER[card.status] + " " +
300
316
  "transition-[border-color,background,transform] duration-150 " +
301
- "hover:border-line-2 hover:bg-surface hover:-translate-y-px " +
302
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", tabIndex: 0, role: "link", "aria-label": card.title, onKeyDown: (e) => {
303
- if (e.key === "Enter" || e.key === " ") {
304
- e.preventDefault();
305
- navigate(`/principles/${card.principleId}`);
306
- }
307
- }, children: [_jsx("span", { className: "absolute -left-[19px] top-4 h-2.5 w-2.5 rounded-full border-2 border-paper " +
308
- STATUS_DOT_BG[card.status], "aria-hidden": "true" }), _jsxs("div", { className: "flex items-center gap-2 mb-2 flex-wrap", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-0.5 " +
309
- STATUS_TEXT[card.status] +
310
- " border-current/20", children: t("principles.status" + card.status.charAt(0).toUpperCase() + card.status.slice(1)) }), card.channels.map((ch) => (_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-3", children: enumLabel('channel', ch, t) }, ch))), card.detectedLanguage && card.detectedLanguage !== 'unknown' && (_jsx("span", { className: "font-mono text-[11px] tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: card.detectedLanguage === 'zh'
311
- ? t("principles.languageHintZh")
312
- : t("principles.languageHintEn") })), _jsxs("span", { className: "font-mono text-[11px] text-ink-4", children: [t("principles.evaluabilityLabel", { defaultValue: "可评价性" }), ": ", enumLabel('evaluability', card.evaluability, t)] }), card.decisionState !== undefined && (_jsx("span", { className: "font-mono text-[11px] tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: t(`principles.ownerDecision.state.${card.decisionState}`, { defaultValue: card.decisionState }) }))] }), card.readabilityWarningCode && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-amber/5 border border-amber/20 rounded-[3px] text-ink-3 text-[12px] leading-snug", children: t("principles.readabilityWarning." + card.readabilityWarningCode, { defaultValue: t("principles.readabilityFallbackTitle") }) })), card.detectedLanguage && card.detectedLanguage !== 'unknown' && i18n.language && !i18n.language.startsWith(card.detectedLanguage) && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-surface/60 border-l-2 border-line rounded-[3px] text-ink-4 text-[12px] leading-snug", children: t("principles.languageMismatchHint", { lang: card.detectedLanguage === 'en' ? 'English' : '中文' }) })), _jsx("h3", { className: "font-semibold text-ink mb-1", children: card.title }), card.titleUsedFallback && card.originalTitle && (_jsxs("details", { className: "mb-1", children: [_jsx("summary", { className: "text-ink-4 text-[11px] font-mono cursor-pointer hover:underline", children: t("principles.readabilityOriginalLabel") }), _jsx("div", { className: "text-ink-4 text-[12px] leading-snug mt-1 font-mono bg-surface/40 px-2 py-1 rounded-[3px] break-all", children: card.originalTitle })] })), _jsx("p", { className: "text-ink-3 text-[13px] line-clamp-3 leading-relaxed", children: card.text }), _jsxs("p", { className: "font-mono text-[11px] text-ink-4 mt-2", children: [t("principles.updatedAt"), ": ", formatDate(card.updatedAt, i18n.language)] }), _jsx(EvolutionBlock, { label: t("principles.blockBasis"), children: t("principles.blockBasisFallback") }), _jsx(EvolutionBlock, { label: t("principles.blockImpact"), children: card.decisionState !== undefined
313
- ? t("principles.blockImpactSeeDecisionView", { defaultValue: "以详情页「当前实际执行」为准(本卡片不据此判断影响)。" })
314
- : t(BLOCK_IMPACT_KEY[card.status]) }), _jsx(EvolutionBlock, { label: t("principles.blockBehavior"), children: card.action ? card.action : t("principles.blockBehaviorFallback") }), _jsx("div", { className: "mt-3 pt-3 border-t border-line/60", children: _jsxs("div", { className: "flex items-center justify-between gap-3 bg-gov/5 border border-line rounded-[3px] px-3 py-2", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] text-ink-4", children: t("principles.govDecision") }), _jsxs("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-0.5 " +
315
- STATUS_TEXT[card.status] +
316
- " border-current/20", children: [decision.glyph, " ", t(decision.labelKey)] }), card.runtimeDeactivated === true && (_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-0.5 border-amber-300 text-amber-700 bg-amber-50", children: t("principles.runtimeDeactivated") }))] }), decisionDate && (_jsx("span", { className: "font-mono text-[11px] text-ink-4 whitespace-nowrap", children: formatDate(decisionDate, i18n.language) }))] }) })] }, card.principleId));
317
+ "hover:border-line-2 " +
318
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", children: [_jsx("span", { className: "absolute -left-[19px] top-4 h-2.5 w-2.5 rounded-full border-2 border-paper " +
319
+ STATUS_DOT_BG[card.status], "aria-hidden": "true" }), _jsxs("div", { className: "mb-2 flex flex-wrap items-start justify-between gap-3", children: [_jsx("h3", { className: "min-w-0 flex-1 text-[15px] font-semibold leading-relaxed text-ink", children: _jsx(Link, { className: "rounded-sm hover:text-gov focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", to: `/principles/${card.principleId}`, state: { returnTo: `/principles?${searchParams}` }, children: card.title }) }), _jsx("span", { className: `rounded-[2px] border px-2 py-1 text-[11px] ${STATUS_TEXT[card.status]}`, children: card.decisionState !== undefined ? t(`principles.ownerDecision.state.${card.decisionState}`, { defaultValue: card.decisionState }) : t(decision.labelKey) })] }), card.readabilityWarningCode && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-amber/5 border border-amber/20 rounded-[3px] text-ink-3 text-[12px] leading-snug", children: t("principles.readabilityWarning." + card.readabilityWarningCode, { defaultValue: t("principles.readabilityFallbackTitle") }) })), card.text.trim() !== card.title.trim() && _jsx("p", { className: "text-ink-3 text-[13px] line-clamp-2 leading-relaxed", children: card.text }), card.runtimeDeactivated === true && _jsx("p", { className: "mt-3 text-[12px] text-amber", role: "status", children: t('principles.runtimeDeactivated') }), _jsxs("details", { className: "mt-4 border-t border-line/60 pt-3", "data-testid": "principle-context-disclosure", children: [_jsx("summary", { className: "cursor-pointer text-[12px] text-gov", children: t('principles.contextDisclosure') }), _jsxs("div", { className: "mt-4 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2 mb-2 flex-wrap", children: [_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border rounded-[2px] px-2 py-0.5 " +
320
+ STATUS_TEXT[card.status] +
321
+ " border-current/20", children: t("principles.status" + card.status.charAt(0).toUpperCase() + card.status.slice(1)) }), card.channels.map((ch) => (_jsx("span", { className: "font-mono text-[11px] uppercase tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-3", children: enumLabel('channel', ch, t) }, ch))), card.detectedLanguage && card.detectedLanguage !== 'unknown' && (_jsx("span", { className: "font-mono text-[11px] tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: card.detectedLanguage === 'zh'
322
+ ? t("principles.languageHintZh")
323
+ : t("principles.languageHintEn") })), _jsxs("span", { className: "font-mono text-[11px] text-ink-4", children: [t("principles.evaluabilityLabel", { defaultValue: "可评价性" }), ": ", enumLabel('evaluability', card.evaluability, t)] }), card.decisionState !== undefined && (_jsx("span", { className: "font-mono text-[11px] tracking-[0.02em] border border-line rounded-[2px] px-2 py-0.5 text-ink-4", children: t(`principles.ownerDecision.state.${card.decisionState}`, { defaultValue: card.decisionState }) }))] }), card.detectedLanguage && card.detectedLanguage !== 'unknown' && i18n.language && !i18n.language.startsWith(card.detectedLanguage) && (_jsx("div", { className: "mb-2 px-3 py-1.5 bg-surface/60 border-l-2 border-line rounded-[3px] text-ink-4 text-[12px] leading-snug", children: t("principles.languageMismatchHint", { lang: card.detectedLanguage === 'en' ? 'English' : '中文' }) })), card.titleUsedFallback && card.originalTitle && (_jsxs("details", { className: "mb-1", children: [_jsx("summary", { className: "text-ink-4 text-[11px] font-mono cursor-pointer hover:underline", children: t("principles.readabilityOriginalLabel") }), _jsx("div", { className: "text-ink-4 text-[12px] leading-snug mt-1 font-mono bg-surface/40 px-2 py-1 rounded-[3px] break-all", children: card.originalTitle })] })), _jsxs("p", { className: "font-mono text-[11px] text-ink-4 mt-2", children: [t("principles.updatedAt"), ": ", formatDate(card.updatedAt, i18n.language)] }), _jsx(EvolutionBlock, { label: t("principles.blockBasis"), children: t("principles.blockBasisFallback") }), _jsx(EvolutionBlock, { label: t("principles.blockImpact"), children: card.decisionState !== undefined
324
+ ? t("principles.blockImpactSeeDecisionView", { defaultValue: "以详情页「当前实际执行」为准(本卡片不据此判断影响)。" })
325
+ : t(BLOCK_IMPACT_KEY[card.status]) }), _jsx(EvolutionBlock, { label: t("principles.blockBehavior"), children: card.action ? card.action : t("principles.blockBehaviorFallback") }), decisionDate && _jsxs("p", { className: "text-[11px] text-ink-4", children: [t('principles.govDecision'), ": ", formatDate(decisionDate, i18n.language)] })] })] }), _jsx(Link, { className: "mt-3 inline-block text-[12px] text-gov underline underline-offset-4", to: `/principles/${card.principleId}`, state: { returnTo: `/principles?${searchParams}` }, children: t('principles.openDetails') })] }, card.principleId));
317
326
  })] }))] }));
318
327
  }
@@ -0,0 +1,30 @@
1
+ import { type ApprovalRecordData } from './validators.js';
2
+ export type ApprovalReviewGuard = {
3
+ schemaVersion: 'approval-review-v1';
4
+ approvalId: string;
5
+ artifactId: string;
6
+ channel: 'prompt' | 'code_tool_hook' | 'defer_archive';
7
+ } & ({
8
+ artifactState: 'present';
9
+ artifactDigest: string;
10
+ } | {
11
+ artifactState: 'missing';
12
+ });
13
+ export interface ApprovalReviewData {
14
+ approval: ApprovalRecordData;
15
+ expectedReview: ApprovalReviewGuard;
16
+ material: {
17
+ availability: 'available' | 'missing' | 'unreadable';
18
+ artifactId: string;
19
+ statement: string | null;
20
+ rationale: string | null;
21
+ targetBehavior: string | null;
22
+ scope: string[];
23
+ risks: string[] | null;
24
+ exactBody: {
25
+ kind: 'principle' | 'rule_code';
26
+ text: string;
27
+ } | null;
28
+ };
29
+ }
30
+ export declare function validateApprovalReview(value: unknown): ApprovalReviewData | null;
@@ -0,0 +1,50 @@
1
+ import { validateApprovalRecordDirect } from './validators.js';
2
+ function record(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); }
3
+ export function validateApprovalReview(value) {
4
+ if (!record(value) || value.writeGuardVersion !== 'approval-review-v1' || !record(value.review))
5
+ return null;
6
+ const approval = validateApprovalRecordDirect(value);
7
+ const guard = value.review.expectedReview;
8
+ const { material } = value.review;
9
+ if (!approval || !record(guard) || guard.schemaVersion !== 'approval-review-v1' || guard.approvalId !== approval.approvalId
10
+ || guard.artifactId !== approval.artifactId || guard.channel !== approval.channel
11
+ || !['prompt', 'code_tool_hook', 'defer_archive'].includes(String(guard.channel))
12
+ || (guard.artifactState !== 'present' && guard.artifactState !== 'missing')
13
+ || (guard.artifactState === 'present' && (typeof guard.artifactDigest !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(guard.artifactDigest)))
14
+ || !record(material) || material.artifactId !== approval.artifactId
15
+ || !['available', 'missing', 'unreadable'].includes(String(material.availability))
16
+ || (material.statement !== null && typeof material.statement !== 'string')
17
+ || (material.rationale !== null && typeof material.rationale !== 'string')
18
+ || (material.targetBehavior !== null && typeof material.targetBehavior !== 'string')
19
+ || !Array.isArray(material.scope) || !material.scope.every(item => typeof item === 'string')
20
+ || (material.risks !== null && (!Array.isArray(material.risks) || !material.risks.every(item => typeof item === 'string'))))
21
+ return null;
22
+ if (guard.artifactState === 'missing' && (material.availability !== 'missing' || material.exactBody !== null || material.statement !== null || Object.hasOwn(guard, 'artifactDigest')))
23
+ return null;
24
+ if (guard.artifactState === 'present' && material.availability === 'missing')
25
+ return null;
26
+ const body = material.exactBody;
27
+ let exactBody = null;
28
+ if (body !== null) {
29
+ if (!record(body) || (body.kind !== 'principle' && body.kind !== 'rule_code') || typeof body.text !== 'string')
30
+ return null;
31
+ exactBody = { kind: body.kind, text: body.text };
32
+ }
33
+ const { channel } = guard;
34
+ if (channel !== 'prompt' && channel !== 'code_tool_hook' && channel !== 'defer_archive')
35
+ return null;
36
+ // The reader treats a material body as present exactly when availability says so;
37
+ // a payload that disagrees must fail loud rather than render the wrong preview.
38
+ if ((material.availability === 'available') !== (exactBody !== null))
39
+ return null;
40
+ if (exactBody !== null && (channel === 'code_tool_hook') !== (exactBody.kind === 'rule_code'))
41
+ return null;
42
+ const expectedReview = guard.artifactState === 'present' && typeof guard.artifactDigest === 'string'
43
+ ? { schemaVersion: 'approval-review-v1', approvalId: approval.approvalId, artifactId: approval.artifactId, channel, artifactState: 'present', artifactDigest: guard.artifactDigest }
44
+ : { schemaVersion: 'approval-review-v1', approvalId: approval.approvalId, artifactId: approval.artifactId, channel, artifactState: 'missing' };
45
+ const { availability } = material;
46
+ if (availability !== 'available' && availability !== 'missing' && availability !== 'unreadable')
47
+ return null;
48
+ return { approval, expectedReview, material: { availability, artifactId: approval.artifactId, statement: material.statement,
49
+ rationale: material.rationale, targetBehavior: material.targetBehavior, scope: material.scope, risks: material.risks, exactBody } };
50
+ }
@@ -16,4 +16,5 @@ export interface ReadingLayout {
16
16
  /** 逐字全文。null 表示原文即短标题、已由 title 完整承载(不重复渲染)。 */
17
17
  body: string | null;
18
18
  }
19
+ export declare function truncateByCodePoints(text: string, max: number): string;
19
20
  export declare function splitReadingTitleBody(text: string | null | undefined): ReadingLayout;
@@ -22,7 +22,7 @@ function firstNonEmptyLine(text) {
22
22
  }
23
23
  return "";
24
24
  }
25
- function truncateByCodePoints(text, max) {
25
+ export function truncateByCodePoints(text, max) {
26
26
  const chars = Array.from(text);
27
27
  if (chars.length <= max)
28
28
  return text;
@@ -637,6 +637,8 @@ export interface ApprovalGroupRecordData {
637
637
  channel: string;
638
638
  createdAt: string;
639
639
  status: string;
640
+ /** This record's own revision review is incomplete; absent = no such defect. */
641
+ revisionReviewUnavailable?: boolean;
640
642
  }
641
643
  export interface ApprovalGroupData {
642
644
  principleId: string;
@@ -650,6 +652,8 @@ export interface ApprovalGroupData {
650
652
  */
651
653
  revisionReviewUnavailable?: boolean;
652
654
  revisionReview?: {
655
+ artifactId?: string;
656
+ artifactDigest?: string;
653
657
  oldStatement: string;
654
658
  newStatement: string;
655
659
  intentFields: Record<string, string>;
@@ -1740,7 +1740,13 @@ function validateApprovalGroupRecord(v) {
1740
1740
  return null;
1741
1741
  if (!Object.hasOwn(v, 'status') || !isString(v.status))
1742
1742
  return null;
1743
- return { id: v.id, artifactId: v.artifactId, channel: v.channel, createdAt: v.createdAt, status: v.status };
1743
+ const record = { id: v.id, artifactId: v.artifactId, channel: v.channel, createdAt: v.createdAt, status: v.status };
1744
+ if (Object.hasOwn(v, 'revisionReviewUnavailable')) {
1745
+ if (typeof v.revisionReviewUnavailable !== 'boolean')
1746
+ return null;
1747
+ record.revisionReviewUnavailable = v.revisionReviewUnavailable;
1748
+ }
1749
+ return record;
1744
1750
  }
1745
1751
  function validateApprovalGroup(v) {
1746
1752
  if (!isObject(v))
@@ -1774,7 +1780,9 @@ function validateApprovalGroup(v) {
1774
1780
  if (typeof value === 'string')
1775
1781
  intentFields[key] = value;
1776
1782
  }
1777
- result.revisionReview = { oldStatement: review.oldStatement, newStatement: review.newStatement, intentFields };
1783
+ result.revisionReview = { oldStatement: review.oldStatement, newStatement: review.newStatement, intentFields,
1784
+ ...(typeof review.artifactId === 'string' ? { artifactId: review.artifactId } : {}),
1785
+ ...(typeof review.artifactDigest === 'string' && /^sha256:[a-f0-9]{64}$/.test(review.artifactDigest) ? { artifactDigest: review.artifactDigest } : {}) };
1778
1786
  }
1779
1787
  // Wave 7: optional candidateDescription from artifact contentJson
1780
1788
  if (Object.hasOwn(v, 'candidateDescription') && isString(v.candidateDescription) && v.candidateDescription.length > 0) {
@@ -434,6 +434,9 @@
434
434
  .mt-6 {
435
435
  margin-top: calc(var(--spacing) * 6);
436
436
  }
437
+ .mt-7 {
438
+ margin-top: calc(var(--spacing) * 7);
439
+ }
437
440
  .mt-8 {
438
441
  margin-top: calc(var(--spacing) * 8);
439
442
  }
@@ -530,11 +533,11 @@
530
533
  .ml-auto {
531
534
  margin-left: auto;
532
535
  }
533
- .line-clamp-3 {
536
+ .line-clamp-2 {
534
537
  overflow: hidden;
535
538
  display: -webkit-box;
536
539
  -webkit-box-orient: vertical;
537
- -webkit-line-clamp: 3;
540
+ -webkit-line-clamp: 2;
538
541
  }
539
542
  .block {
540
543
  display: block;
@@ -644,9 +647,6 @@
644
647
  .h-screen {
645
648
  height: 100vh;
646
649
  }
647
- .max-h-32 {
648
- max-height: calc(var(--spacing) * 32);
649
- }
650
650
  .max-h-40 {
651
651
  max-height: calc(var(--spacing) * 40);
652
652
  }
@@ -801,6 +801,9 @@
801
801
  .max-w-\[400px\] {
802
802
  max-width: 400px;
803
803
  }
804
+ .max-w-\[440px\] {
805
+ max-width: 440px;
806
+ }
804
807
  .max-w-\[480px\] {
805
808
  max-width: 480px;
806
809
  }
@@ -816,6 +819,12 @@
816
819
  .max-w-\[760px\] {
817
820
  max-width: 760px;
818
821
  }
822
+ .max-w-\[1120px\] {
823
+ max-width: 1120px;
824
+ }
825
+ .max-w-full {
826
+ max-width: 100%;
827
+ }
819
828
  .max-w-lg {
820
829
  max-width: var(--container-lg);
821
830
  }
@@ -1079,6 +1088,11 @@
1079
1088
  margin-block-start: calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));
1080
1089
  margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
1081
1090
  }
1091
+ :where(.space-y-7 > :not(:last-child)) {
1092
+ --tw-space-y-reverse: 0;
1093
+ margin-block-start: calc(calc(var(--spacing) * 7) * var(--tw-space-y-reverse));
1094
+ margin-block-end: calc(calc(var(--spacing) * 7) * calc(1 - var(--tw-space-y-reverse)));
1095
+ }
1082
1096
  :where(.space-y-8 > :not(:last-child)) {
1083
1097
  --tw-space-y-reverse: 0;
1084
1098
  margin-block-start: calc(calc(var(--spacing) * 8) * var(--tw-space-y-reverse));
@@ -1396,6 +1410,12 @@
1396
1410
  border-color: color-mix(in oklab, var(--color-green) 40%, transparent);
1397
1411
  }
1398
1412
  }
1413
+ .border-ink-3\/30 {
1414
+ border-color: color-mix(in srgb, #525966 30%, transparent);
1415
+ @supports (color: color-mix(in lab, red, red)) {
1416
+ border-color: color-mix(in oklab, var(--color-ink-3) 30%, transparent);
1417
+ }
1418
+ }
1399
1419
  .border-ink-4 {
1400
1420
  border-color: var(--color-ink-4);
1401
1421
  }
@@ -1576,6 +1596,12 @@
1576
1596
  .bg-ink-3 {
1577
1597
  background-color: var(--color-ink-3);
1578
1598
  }
1599
+ .bg-ink-3\/10 {
1600
+ background-color: color-mix(in srgb, #525966 10%, transparent);
1601
+ @supports (color: color-mix(in lab, red, red)) {
1602
+ background-color: color-mix(in oklab, var(--color-ink-3) 10%, transparent);
1603
+ }
1604
+ }
1579
1605
  .bg-ink-4 {
1580
1606
  background-color: var(--color-ink-4);
1581
1607
  }
@@ -1798,6 +1824,9 @@
1798
1824
  .py-8 {
1799
1825
  padding-block: calc(var(--spacing) * 8);
1800
1826
  }
1827
+ .py-10 {
1828
+ padding-block: calc(var(--spacing) * 10);
1829
+ }
1801
1830
  .py-12 {
1802
1831
  padding-block: calc(var(--spacing) * 12);
1803
1832
  }
@@ -2033,6 +2062,10 @@
2033
2062
  --tw-leading: 1.62;
2034
2063
  line-height: 1.62;
2035
2064
  }
2065
+ .leading-\[1\.85\] {
2066
+ --tw-leading: 1.85;
2067
+ line-height: 1.85;
2068
+ }
2036
2069
  .leading-none {
2037
2070
  --tw-leading: 1;
2038
2071
  line-height: 1;
@@ -2254,9 +2287,6 @@
2254
2287
  .opacity-72 {
2255
2288
  opacity: 72%;
2256
2289
  }
2257
- .opacity-80 {
2258
- opacity: 80%;
2259
- }
2260
2290
  .opacity-100 {
2261
2291
  opacity: 100%;
2262
2292
  }
@@ -2499,9 +2529,6 @@
2499
2529
  --tw-content: '';
2500
2530
  content: var(--tw-content);
2501
2531
  }
2502
- .last\:mb-0:last-child {
2503
- margin-bottom: 0px;
2504
- }
2505
2532
  .last\:border-r-0:last-child {
2506
2533
  border-right-style: var(--tw-border-style);
2507
2534
  border-right-width: 0px;
@@ -2529,6 +2556,14 @@
2529
2556
  .hover\:border-gov:hover {
2530
2557
  border-color: var(--color-gov);
2531
2558
  }
2559
+ .hover\:border-gov\/50:hover {
2560
+ border-color: color-mix(in srgb, #1e3a5f 50%, transparent);
2561
+ }
2562
+ @supports (color: color-mix(in lab, red, red)) {
2563
+ .hover\:border-gov\/50:hover {
2564
+ border-color: color-mix(in oklab, var(--color-gov) 50%, transparent);
2565
+ }
2566
+ }
2532
2567
  .hover\:border-line:hover {
2533
2568
  border-color: var(--color-line);
2534
2569
  }
@@ -3301,6 +3336,79 @@
3301
3336
  }
3302
3337
  color: var(--color-amber);
3303
3338
  }
3339
+ .owner-workbench-container {
3340
+ container-type: inline-size;
3341
+ }
3342
+ .owner-workbench {
3343
+ display: grid;
3344
+ gap: 28px;
3345
+ }
3346
+ .owner-work-reading {
3347
+ border-top: 1px solid var(--color-line);
3348
+ padding-top: 24px;
3349
+ scroll-margin-top: 80px;
3350
+ }
3351
+ .owner-workbench:has(.owner-work-list-selected) .owner-work-list {
3352
+ display: none;
3353
+ }
3354
+ .owner-work-list {
3355
+ max-height: 52dvh;
3356
+ overflow-y: auto;
3357
+ padding-right: 8px;
3358
+ scrollbar-gutter: stable;
3359
+ scrollbar-width: thin;
3360
+ scrollbar-color: var(--color-ink-4) transparent;
3361
+ }
3362
+ @supports selector(::-webkit-scrollbar) {
3363
+ .owner-work-list {
3364
+ scrollbar-width: auto;
3365
+ scrollbar-color: auto;
3366
+ }
3367
+ .owner-work-list::-webkit-scrollbar {
3368
+ width: 6px;
3369
+ }
3370
+ .owner-work-list::-webkit-scrollbar-track {
3371
+ background: transparent;
3372
+ }
3373
+ .owner-work-list::-webkit-scrollbar-thumb {
3374
+ background: var(--color-ink-4);
3375
+ border-radius: 999px;
3376
+ }
3377
+ .owner-work-list:hover::-webkit-scrollbar-thumb {
3378
+ background: var(--color-ink-3);
3379
+ }
3380
+ .owner-work-list::-webkit-scrollbar-button {
3381
+ display: none;
3382
+ width: 0;
3383
+ height: 0;
3384
+ }
3385
+ }
3386
+ @media (forced-colors: active) {
3387
+ .owner-work-list {
3388
+ scrollbar-color: auto;
3389
+ }
3390
+ .owner-work-list::-webkit-scrollbar-thumb {
3391
+ background: ButtonText;
3392
+ }
3393
+ }
3394
+ @container (min-width: 900px) {
3395
+ .owner-workbench {
3396
+ grid-template-columns: 272px minmax(0, 1fr);
3397
+ align-items: start;
3398
+ }
3399
+ .owner-workbench:has(.owner-work-list-selected) .owner-work-list {
3400
+ display: block;
3401
+ }
3402
+ .owner-work-list {
3403
+ max-height: 72dvh;
3404
+ }
3405
+ .owner-work-reading {
3406
+ border-top: 0;
3407
+ border-left: 1px solid var(--color-line);
3408
+ padding-top: 0;
3409
+ padding-left: 28px;
3410
+ }
3411
+ }
3304
3412
  @property --tw-translate-x {
3305
3413
  syntax: "*";
3306
3414
  inherits: false;