From 32c5a2ede4e0d89cde40bc3662f08c3635935149 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 30 Jul 2026 19:41:41 +0200 Subject: [PATCH] Attributes UX: Key (type) labels, filter, Overlay without field target. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CE-WP-0010: rename form vocabulary in user-facing strings to attributes, show type next to keys, filter attributes at ≥3 characters, and allow card→highlight guide lines when no attribute is focused. --- src/FieldDefinitionForm.tsx | 7 +- src/FormRenderer.dom.test.tsx | 31 +++++++-- src/FormRenderer.tsx | 119 ++++++++++++++++++++++++++-------- src/index.ts | 3 +- src/visual-guide/Overlay.tsx | 30 ++++----- 5 files changed, 137 insertions(+), 53 deletions(-) diff --git a/src/FieldDefinitionForm.tsx b/src/FieldDefinitionForm.tsx index cbedb39..e8aeb42 100644 --- a/src/FieldDefinitionForm.tsx +++ b/src/FieldDefinitionForm.tsx @@ -1,5 +1,6 @@ /** - * Shared label + type editor for add-field and edit-field flows (CE-WP-0007-T10/T11). + * Shared key + type editor for add/edit attribute flows + * (CE-WP-0007-T10/T11; CE-WP-0010 attributes vocabulary). * Styled to match EvidenceFormBody / InlineCaptureForm. */ @@ -48,7 +49,7 @@ export function FieldDefinitionForm(p: FieldDefinitionFormProps) {
{p.badge}
)} setFilterQuery(e.target.value)} + placeholder="Filter…" + aria-label="Filter attributes" + data-testid="attributes-filter" + style={{ + flex: "1 1 100px", + minWidth: 80, + maxWidth: 160, + fontSize: 12, + padding: "4px 6px", + border: "1px solid #ccc", + borderRadius: 4, + }} + /> @@ -286,13 +344,22 @@ export function FormRenderer({ }) } onCancel={() => onCancelAddField?.()} - saveLabel="Add field" - badge="New form field" + saveLabel="Add attribute" + badge="New attribute" testidPrefix="field-add" /> )} - {schema.fields.map((field) => ( + {visibleFields.length === 0 && schema.fields.length > 0 && ( +

+ No matches. +

+ )} + + {visibleFields.map((field) => ( onCancelFieldEdit?.()} /> ))} - + ); -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index a2e88f4..a0c7754 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,8 +2,9 @@ export * from "./repos"; export * from "./services"; export * from "./state"; export * from "./visual-guide"; -export { FormRenderer } from "./FormRenderer"; +export { FormRenderer, matchesTextFilter } from "./FormRenderer"; export type { + FieldDefinitionPatch, FormFieldSchema, FormRendererProps, FormSchema, diff --git a/src/visual-guide/Overlay.tsx b/src/visual-guide/Overlay.tsx index aa257bf..e0fa2ad 100644 --- a/src/visual-guide/Overlay.tsx +++ b/src/visual-guide/Overlay.tsx @@ -11,10 +11,10 @@ * `useSyncExternalStore` subscription via `useRectRegistryVersion` picks * up that single tick and React re-renders once per frame. * - * Active-only: only the currently active triple is drawn. If any leg's - * rect is missing (e.g. the viewer hasn't reported a highlight rect for - * the active annotation yet), that leg is omitted but the other one - * still renders. + * Active-only: draws legs for the active evidence card. Attribute→card + * when a Capture attribute is focused; card→highlight whenever the + * highlight rect is available (Annotate or Capture). Missing legs are + * omitted independently. * * MVP-sufficient. Future polish: easing the curve direction by source * type, animating the transition between active states, dimming @@ -33,14 +33,6 @@ function rectCenter(rect: DOMRect): { x: number; y: number } { return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }; } -function rectBottomCenter(rect: DOMRect): { x: number; y: number } { - return { x: rect.left + rect.width / 2, y: rect.bottom }; -} - -function rectTopCenter(rect: DOMRect): { x: number; y: number } { - return { x: rect.left + rect.width / 2, y: rect.top }; -} - /** * Build a quadratic bezier from `a` to `b` whose control point bulges * horizontally between them. The horizontal-bulge style is right for a @@ -72,18 +64,24 @@ export function Overlay({ const version = useRectRegistryVersion(); const paths = useMemo(() => { - if (!state.activeTarget || !state.activeEvidenceItemId) return []; - const fieldRect = registry.getRect("field", state.activeTarget.targetId); + // CE-WP-0010: card → highlight is enough for Annotate mode (no attribute + // target). Field → card still draws when Capture has an active target. + if (!state.activeEvidenceItemId) return []; + const fieldRect = state.activeTarget + ? registry.getRect("field", state.activeTarget.targetId) + : null; const cardRect = registry.getRect("evidence-card", state.activeEvidenceItemId); const highlightRect = state.activeAnnotationId ? registry.getRect("highlight", state.activeAnnotationId) : null; const out: string[] = []; if (fieldRect && cardRect) { - out.push(bezierPath(rectBottomCenter(fieldRect), rectTopCenter(cardRect))); + // Horizontal layout (Evidence | Attributes): centre-to-centre reads better + // than the old bottom-strip geometry. + out.push(bezierPath(rectCenter(fieldRect), rectCenter(cardRect))); } if (cardRect && highlightRect) { - out.push(bezierPath(rectTopCenter(cardRect), rectCenter(highlightRect))); + out.push(bezierPath(rectCenter(cardRect), rectCenter(highlightRect))); } void version; // memo invalidator return out;