astra
alpha-0.1-background
alpha-0.1-dir-prefs
alpha-0.1-sec-dom
menubar-v4-safe
active-uix
morfo-runtime
morfo-driven-soma
semantuix
glm-5
main
sium-v1.0
${ noResults }
15 Commits (b3524b3446ce41d49212501012da9faa9046d126)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
b3524b3446 |
fix(words): image block selection — click figure activates image drawer mode (COLOR-1)
Image blocks are contenteditable=false so the text caret can never live inside them. The drawer never entered image mode and the user had no visible "selected image" state. Fixed with a separate atomic- block selection layer: - New provider state `selectedBlockIndex` (number | undefined) for atomic block selection. Distinct from text selection. - `currentBlock` derived now short-circuits to the atomic block's type when `selectedBlockIndex` is set, so the drawer enters image mode whenever the user has selected an image. - New methods `selectAtomicBlock(blockIndex)` + `clearSelectedBlock()` on the provider. selectAtomicBlock also focuses the editor root so keyboard handlers (esc, delete) work on the atomic block. - `syncSelectionFromDom` clears the atomic-block highlight only when the text selection actually moved (was clearing on every selectionchange echo, which wiped the highlight a tick after the click landed). - Provider onclick handler detects clicks on figure[data-words-block= 'image'][data-words-path], decodes the path, calls selectAtomicBlock(blockIndex). - Render passes `selectedBlockIndex` through findOpts; image block render adds `data-words-block-selected` attr when the index matches. - Eidos CSS paints a 3px accent outline + glow on the selected figure. Image panel's imageBlockAtCaret() now falls back to selectedBlockIndex first, then text-selection anchor. Result: clicking an image in the editor now highlights it (accent outline + glow), the drawer enters IMAGE mode and shows its source/alt/caption/status panel. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
e25ac301b5 |
feat(words): drawer hierarchical mode stack — Format → Cell → Row → Table (HIER 1-3)
The drawer now renders the FULL hierarchy of scopes that apply at the caret, instead of only the most-specific one. Inside a table cell with text selected the user sees four panels stacked: Format ← active selection's inline marks/links Cell ← tone, vertical align, cell text-align Row ← +/- row, row header toggle Table ← striped, compact, +/- column, col header Each panel is independently collapsible. Default: the leaf (most specific) is open, the rest are collapsed but accessible via the chevron next to their header. User-explicit toggles persist across context changes. HIER-1 — soma: - WordsDrawerMode union expanded: split 'table' → 'cell' | 'row' | 'table'; split 'list' → 'list-item' | 'list'; added 'block' for the paragraph/heading/quote text-align scope. - WordsDrawerProvider gains `modes: readonly DrawerMode[]` derived that walks the caret context outward (most-specific first). The legacy `mode` field is kept as the top of the stack for consumers that just want the leaf. - snippetProps surfaces both `modes` (stack) and `mode` (leaf). - Morfo `data-words-drawer-mode` values updated to the new union. HIER-2 — eidos panel split: - Table panel decomposed into three: cell-scope (tone + vertical + text-align), row-scope (+/- row, row header), table-scope (+/- col, col header, striped, compact). - List panel decomposed into list-item-scope (indent/outdent) + list-scope (kind). - New 'block' panel hosts text-align when caret is in a heading / paragraph / quote outside a table cell. - Each panel is a `<section data-words-drawer-panel data-depth=N>` with its own toggleable header + body snippet. HIER-3 — per-panel collapse: - SvelteMap stores user-explicit open/closed overrides per mode. isPanelOpen() falls back to "open if leaf, else collapsed" when no override exists. - Drawer header now shows leaf title in bold + breadcrumb of deeper panels in muted text (Format · Cell · Row · Table). - Visual depth cue: border-inline-start of each panel renders at decreasing strength as depth grows (leaf solid, deeper at 60% / 30% accent border). Visual verification: selected text inside a sample table cell shows 4 panels stacked (Format open, Cell/Row/Table collapsed), header breadcrumb "FORMAT · Cell · Row · Table". Click on any collapsed header expands it. 214/214 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
4 months ago |
|
|
9a5f3c2359 |
refactor(words): drawer canon — morfo part + soma provider + eidos consumes via provider (DRAWER-CANON)
Closes the architectural gap from the DRAWER spike: the companion
drawer now follows the same morfo/soma/eidos pattern as every other
Words part (Toolbar/BubbleMenu/SlashMenu/LinkEditor/Status/etc.).
DRAWER-CANON-1 — morfo + soma:
- wordsMorfo gains a 'Drawer' part (kebab 'drawer', archetype 'group',
defaultElement 'aside', role 'complementary'). Declares data-words-
drawer-mode (default/format/image/table/code/list), data-open,
data-disabled, aria-label. New event 'commit-toggle-drawer' fires
on collapse/expand for sema/telemetry hooks.
- New WordsDrawerProvider in words-provider.svelte: owns `open`
bindable state, derives `mode` from parent provider's currentBlock
+ selection (same logic as the spike, now in soma where it
belongs), exposes snippetProps {mode, open, disabled, snippet,
toggle, setOpen}. `toggle()` and `setOpen()` emit the event.
- WordsDrawerProps + WordsDrawerSnippetProps + WordsDrawerMode types
added to types.ts and re-exported via exports.ts.
- soma/components/words/components/words-drawer.svelte (the soma
component) mounts the provider and forwards merged props to either
the `child` snippet or the default <aside> render.
DRAWER-CANON-2 — eidos refactor:
- eidos words-drawer.svelte rewritten as a consumer of
<Words.Drawer>. The shell (aside + header + collapse button) and
the per-mode body now live inside the children snippet that
receives the soma's drawer snippet props. Mode derivation removed
from eidos (it's now in soma). Local open $state removed (now
bindable via the soma component).
- Demo +page.svelte: `<Words.Drawer snippet={snippetProps} />`
collapses to plain `<Words.Drawer />` — the soma provider hands the
parent snippet down internally.
DRAWER-CANON-3 — verify:
- 214/214 tests pass in soma+morfo scope (no regressions).
- npx tsc --noEmit clean for words area.
- Visual verification: drawer still renders with DOCUMENT/OUTLINE/
STATS/SUGGESTIONS in default mode; context-switching to table /
code / list / format still works; aside element now carries
role='complementary', aria-label from morfo translationRef,
data-words-drawer-mode reflecting current panel.
The eidos drawer now passes the same canon checks as every other
Words part: morfo declares its contract, soma owns the state, eidos
only paints. The schema validator can verify the drawer's attrs, the
eidos lint can check selector usage, sema can react to the toggle
event.
Demo header chips reflect the new contract: PARTS 19→20, EVENTS
11→12.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
ef95b1a82a |
feat(words): slash command /image + paste/drop with onUploadImage (F3.7, F3.8)
F3.7 — slash command /image:
- DEFAULT_SLASH_COMMANDS gains an 'image' entry: triggers a window
prompt for the URL (and optional alt text), then dispatches
insertImage. The native prompt is MVP — consumers wanting a custom
dialog can hide the slash entry via slashCommands override and call
applyWordsCommand({ type: 'insertImage', src }) from their own UI.
- WordsSlashCommandId union + WordsCommandName union extended with
'image' / 'insert-image' respectively.
- slashCommandToWordsCommand returns undefined for 'image' so the
prompt path in commitSlashCommand owns it.
F3.8 — paste/drop with onUploadImage callback:
- New WordsProps.onUploadImage prop: (file: File) => Promise<{url, alt?}>.
Consumer-injected upload pipeline (S3, R2, own backend, etc.).
- onpaste detects image files in clipboardData.files. If present + the
callback is wired, intercepts the paste (drops text/html processing)
and inserts each image with a blob: URL + status='pending'.
- New ondragover + ondrop handlers gate on dataTransferHasImageFiles
and the callback being set; trigger insertImageFiles on drop.
- insertImageFiles inserts pending placeholders, awaits the upload
promise for each, then either swaps src for the final URL (success)
or flips status='error' (rejection). Matches blocks by blob URL
(unique) so concurrent uploads + concurrent edits stay coherent.
- replaceImageBlock / markImageBlockError use mapImageBlocksBySrc to
rewrite the document via replaceDocument command. No new command
type needed.
- Helpers added: collectImageFiles, dataTransferHasImageFiles,
mapImageBlocksBySrc — all pure functions at module scope.
Test fixture: wordsOpts gains the onUploadImage state slot so the 16
existing provider tests typecheck without behavior change.
152/152 tests pass in the full words soma scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
08bef77f4b |
feat(words): image extension foundation — types, render, HTML+Markdown serializers (F3.1-F3.5)
Adds image as a first-class block node to the words engine. Phase F3
of the words rich-text editor. Ships the bottom half of the stack so
images can be authored in source (markdown / HTML / value JSON) and
roundtripped, but UX surfaces (toolbar action, slash command, paste/
drop) land in subsequent commits.
F3.1 — extensions/image/{types,factories,index}.ts:
- WordsImageBlock = { type: 'image', src, alt?, caption?, width?, height?, align?, status? }
- createImage(src, options) factory canonicalising defaults (align='center' stripped, etc.)
- WordsImageStatus = 'pending' | 'error' for upload lifecycle
- WORDS_IMAGE_ALIGNS + isWordsImageAlign predicate
F3.2 — engine/document.ts:
- WordsBlockType + WordsBlock union extended with 'image'
- Image types re-exported (backward-compat with the table-types pattern)
- getBlockText: image → alt text (captions excluded; image stays a
single atom in plain-text contexts)
F3.3 — engine/render.ts:
- Image branch renders <figure data-words-block=image contenteditable=false>
<img src alt width? height? loading=lazy draggable=false>
<figcaption>caption</figcaption?
- WordsRenderTag union gains 'figure' | 'img' | 'figcaption'
- data-words-image-align / data-words-image-status surface state
- renderBlockPlainText: image → alt
F3.4 — engine/serialize-html.ts:
- Out: <figure data-words-image-align?><img alt src width? height?><figcaption?></figure>
- In: 'figure' added to BLOCK_TAGS; <figure><img> elements parsed back
to ImageBlock (with caption from <figcaption>); bare <img> parses
to a block too
- htmlElementText helper added for caption text extraction
F3.5 — engine/serialize-markdown.ts:
- Out:  — escapes ], ( and ) in src, " in caption
- In: standalone line matching ^$ promotes to a
block image. Inline images mid-paragraph become text + no image
(lossy by design — keep the model tight; inline image is a separate
node type if added later)
Engine ripple — exhaustive switches updated:
- normalize.getInlineBlockText
- path.collectContainerChildren (image marked as terminal)
- selection.collectBlockText (image returns empty — opaque atom)
- operations:
- setBlock skips image (image is not convertible to inline-text blocks)
- setTextAlign skips image (no text align on image)
- deleteRange refuses if range crosses an image (atomic)
- insertParagraph on image inserts a fresh paragraph after it and
moves the caret
- blockTextAlign() returns undefined for image
- words-provider.currentTextAlign returns 'left' on image
149/149 tests pass in the full words soma scope. `npx tsc --noEmit`
clean for the touched area. Engine consumers compile unchanged.
Next: F3.6 insertImage operation + toolbar/slash command, F3.7 slash
menu /image entry, F3.8 paste/drop with onUploadImage callback, F3.9
eidos CSS, F3.10 imageExtension stub, F3.11 demo + verify.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
58c2c0babf |
fix(words): close audit P0 + quiet focus event (FASE 1)
Cierre de los hallazgos P0/P1 del audit Codex sobre Words + fix del
evento contact-focus molesto reportado por el usuario.
F1.1 — Fix evento foco (UX):
El handler onfocus disparaba `runtime.trigger('contact-focus')` cada vez
que el editor recibía foco, generando sound + haptic + visual stamp en
cada re-focus desde popovers internos (bubble menu / link editor / find
panel / slash menu / toolbar dropdowns). En sesión normal de edición el
foco entra/sale del content docenas de veces, lo que producía fatiga
perceptual.
Cambios en words-provider.svelte.ts:
- onfocus: emit solo en transición real (was-not-focused → focused).
Re-focus desde popover interno NO emite.
- onblur: si el related target es interno al Words tool tree
(isInsideWordsTool), mantener focused=true (Words se trata como un
solo scope de foco). Skip commit('blur') también para evitar
commits espurios entre popover hops.
F1.2 — Declarar 8 data-* hardcoded en morfo (audit P0-1):
Añadidos al Content part:
- data-words-find-match, data-words-find-active (mark highlights)
- data-words-indent (list items con indent > 0)
- data-language (alias parsing-only para interop con HTML externo
emitido por Prism/highlight.js)
Nuevas 6 parts compound (Eidos wrappers):
- HeadingPicker + HeadingPickerPanel (data-words-heading-picker /
-panel)
- CodeLanguagePicker + CodeLanguagePanel
- ToolbarFamilyMenu + ToolbarFamilyPanel
F1.3 — Rename i18n keys a kebab-case (audit P0-1):
4 keys camelCase eran ilegales según contracts.test.ts. Renamed:
- components.words.bubbleMenu → bubble-menu
- components.words.slashMenu → slash-menu
- components.words.linkEditor → link-editor
- components.words.findReplace → find-replace
Propagado a: morfo declarations (8 refs), provider tslangs calls
(4 refs), catalog langs/components/words.ts (4 keys).
F1.4 — Canonizar 9 event names a forma {family}-{verb}[-{variant}]
(audit P2-1):
Renombrados para parsear con el morfo:vocabulary script. El verb
declarado en cada semantic ahora aparece explícito en el name:
- commit-content → commit-save-content (verb: save)
- commit-format → commit-set-format (verb: set)
- commit-link → commit-set-link
- commit-unlink → commit-reset-link (verb: reset)
- commit-slash-command → commit-set-slash
- commit-check → commit-set-check
- commit-history → commit-restore-history (verb: restore)
- commit-clear → commit-reset-content (verb: reset)
- signal-invalid-input → signal-warn-invalid (verb: warn)
Propagado a: sema cascade selectors, soma provider trigger calls,
provider + content tests, demo route. 0 conflictos con event names
de otros componentes (file-upload/tag-group/etc. usan namespaces
propios sin colisión con words).
Tests: 161/161 pass en src/uix/{contracts,soma/components/words}.
Suite full eidos+morfo+soma+sema pendiente de verificar tras commit.
contracts.test.ts ahora verde — cierra P0 audit Codex.
morfo:vocabulary 0 event-name warnings (los 107 data-attr enum
warnings son pre-existentes, separate scope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
9c6375c96e |
feat(eidos): TSC v2.2 (parts + composition) + universal theming coverage
Token Scope Contract universal — no más excepciones arquitectónicas.
Los 3 componentes que vivían fuera de TSC v2.1 (select, avatar,
toggle-group) ahora están dentro del contrato via dos extensiones nuevas.
TSC v2.2 extensiones (lib/config-types.ts + render-css.ts + config.ts):
- `parts: readonly string[]` en RecipeTokenMultiDeclaration — emite
selectores comma-separados (`[data-{c}-x], [data-{c}-y]`) para
componentes con data-color cascadeado per-part. Consumer: select.
- `composition: { foreignRecipe: { targetSelector, tokens } }` sibling
key — overrides cross-recipe scoped a la cascade del host. Consumer:
toggle-group modifica `--toggle-palette-*` en sus items.
Migraciones:
- select: 3 private `_accent-{track,border,text}` con parts: ['trigger',
'content'] + 7 cascades color:X each. Removed orphan `_accent-solid`
(CSS no consumía).
- avatar: 6 tokens via composite scopes `['variant:X', 'color:Y']` con
matrix helper inline. Badge usa parts: ['badge']. Reemplaza 24 bloques
CSS × 2 partes.
- toggle-group: composition block con 8 palette tokens × 4 colors.
Reemplaza 4 bloques CSS per-color.
Bug toggle-group post-composition (encontrado y arreglado):
Tras la composition migration, el cascade del toggle-group seguía roto
porque los tokens derivados (`--toggle-solid-on-bg`, `--toggle-outline-fg`,
etc.) viven en scope `[data-toggle]`. El `[data-toggle-group-item]` es
sibling (no descendant), así que `var(--toggle-solid-on-bg)` resolvía
undefined. Fix: inlined derivation expressions directamente en
`[data-toggle-group-item]` y sus variant cascades (solid/outline/ghost),
referenciando palette tokens en su propio scope local.
Validador + emisor + contract:
- `validateRecipeComposition` valida el shape `{ targetSelector, tokens }`
y rechaza composition entries con scope='root'.
- `stripCompositionKey` + `emitComposition` separan el pipeline.
- `appendRecipeContractTokens` skip-list para `composition` (no aparece
como fake `--{c}-composition` knob).
- `tokenKeys`/`tokenEntries` helpers en recipe-css-contract.test.ts
filtran composition en todos los iteradores.
Documentación:
- THEMING.md §18 reescrito como "Cobertura universal de TSC". §7
extendido con subsecciones "Multi-part scope" y "Cross-recipe
composition" + ejemplos completos. TOC actualizado.
- eidos/README.md tabla de referencia ampliada con TSC v2.2 + §18.
- CLAUDE.md gana hand-off "2026-05-27 #5" (TSC v2.2 + cobertura universal).
- CONTINUE.md reescrito al estado actual de la sesión.
Working tree también incluye sprint Words en paralelo (multiple authors):
slash menu, find/replace regex, code language picker, table audit,
toolbar family menu, code highlight engine.
Tests: 786/786 pass en src/uix/{eidos,morfo,soma,sema}. `npm run check`:
6 errores pre-existentes (lib/_demo, soma/components/internal,
web/routes/active) no relacionados.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
4 months ago |
|
|
e6eee766ee |
Improve words editor tooling
|
5 months ago |
|
|
0804749147 |
Words: text alignment, heading picker, ToolPopover cleanup, word boundary fixes
- Add textAlign to document model (paragraph, heading, quote) - Add setTextAlign engine operation with left/center/right/justify - Add alignment toolbar buttons with icons (TextAlignStart, Center, End, Justify) - Update formatting and full presets to include alignment - Fix sameBlockShape to compare textAlign correctly - Add heading levels h2/h3 with Words.HeadingPicker dropdown (Popover) - Remove Words.ToolPopover component; migrate LinkEditor to native Popover - Fix word boundary algorithm: Intl.Segmenter + CJK/punctuation fallback - Fix bubble menu isSelecting logic and pointer event forwarding - Add 5 engine tests for setTextAlign - Update demo presets for alignment - Words engine tests: 42/42 passing |
5 months ago |
|
|
12a7e5cfd3 |
fix(uix audit): apply Kim audit remediations across morfo/soma/eidos/recipe layers
Resolves 34 of 40 failures from src/uix/audit-uix-kimi-1.md. The remaining
6 are all Words component (excluded per project owner — actively developed
on a separate track) or unrelated infra (cookie-adapter-client jsdom timeout).
Key changes:
- soma/provider/context.ts: swallow Svelte's `lifecycle_outside_component`
in `context.set()` so unit tests can instantiate providers without a
component tree (unblocked 18 picker tests in one line).
- soma/textarea: route inline style writes through `dom.apply({attrs})`
instead of direct `el.style.X = Y` (contract regex compliance).
- soma/picker-shell: add minimal Provider component + morfo so the shared
handle utility passes the morfo/component contract.
- eidos/_layout: lift `shared.ts` to `eidos/lib/layout-helpers.ts` (the
`_layout` directory wasn't a real component) and update 8 layout
primitive imports.
- eidos/command: replace `onMount` with `$effect` (visual layer can't use
Svelte 4 lifecycle hooks per component-api contract); restructure
`Item.Icon`/`Item.Shortcut` namespace so contract regex matches.
- eidos/color-picker: extract hue spectrum hsl literals to sidecar
`color-picker-spectrum.css`; keep main recipe color-literal-free.
- eidos/lib/recipes/base.ts: declare 196 layout-primitive + chrome CSS
vars (box, flex, grid, float, group, banner, container, section,
separator, toggle-group, pin-input, scroll-area, splitter,
navigation-menu, dropdown-menu, context-menu, date-range-field,
time-range-field, plus per-component additions to date-field,
color-picker, button, select). 197 missing → 0 non-Words.
- morfo: add `data-dir` to carousel, `data-kind` to date-picker, add
StrengthMeterLabel sub-part to password-field, register picker-shell.
- 5 pickers (date/color/date-range/time/time-range): add `internals.ts`
indirection so `exports.ts` doesn't re-export `*provider.svelte.ts`
directly (contract D); rewrite virtual root types to plain object
literals (no `WithChild`/`PrimitiveDivAttributes`).
- 4 new READMEs (button, textarea, password-field, picker-shell)
following the search-field canonical template.
Test result: 2391/2397 passing (99.75%). Remaining 6 failures are all
Words-scope or jsdom infra timeout — explicitly out of scope.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
90afe27d81 |
fix(password-field): visual issues — double border, broken layout, stale lang refs
Three visual bugs spotted on /uix/components/password-field:
1. **Double-bordered inputs.** The demo wrapped `<PasswordField>` inside
`<Field.Control>`, which adds its own border + padding. PasswordField
IS the field's input shell — it ships its own chrome. Removed the
five offending Field.Control wrappers from login/registration/change
forms; PasswordField now sits directly inside `<Field>`.
2. **Icon wrapping to a new row + StrengthMeter sharing the bordered
box.** Replaced `display: inline-flex; flex-wrap: wrap` on the root
with `display: grid; grid-template-columns: minmax(0, 1fr) auto auto`
so Input + CapsLockIndicator + VisibilityTrigger stay on the first
row. When StrengthMeter is composed, `:has()` switches the root to a
2-row grid where the bordered chrome lifts onto a `::before`
pseudo-element that only spans the first row, and the meter sits
below as a sibling. State chrome (`data-focused`, `data-invalid`)
propagates to the pseudo via the same `:has()` selectors.
3. **CapsLock pill claiming space even when inactive.** Was
`color: transparent; background: transparent` which still consumed
width in the grid. Now `display: none` by default and
`display: inline-flex` only when `[data-active]` is set.
Also fixed: `#?sium.errors.min_length|…` and friends rendered literal
in the demo because the docs shell didn't register `$sium/langs/langs`.
Registered `siumLangs` under the `sium` namespace in the layout's
`createActiveUix({ langs: { schema: { sium: siumLangs } } })`.
svelte-check: 0 errors. Page now loads at 200.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
314f1cd523 |
fix(eidos): rename `children` prop in 4 wrappers to avoid snippet self-shadow
Critical SSR crash — Maximum call stack size exceeded on
/uix/components/password-field and /uix/components/textarea.
Root cause: DEMO_AUTHORING_GUIDE §12.4 pitfall.
When a wrapper does:
let { children, ...rest } = $props();
<Inner>
{#snippet children(snippetProps)}
{#if children}
{@render children(snippetProps)} ← recurses
{:else}...{/if}
{/snippet}
</Inner>
The inner `{#snippet children}` SHADOWS the `children` prop. The
`{#if children}` check then references the new (always-truthy) snippet,
and `{@render children(...)}` recursively renders itself → infinite
loop, RangeError at SSR.
Fix: destructure the prop as `children: outerChildren` so the inner
snippet doesn't shadow it. Same pattern used in drawer wrappers per
the guide.
Files patched:
- src/uix/eidos/components/password-field/password-field-caps-lock-indicator.svelte
- src/uix/eidos/components/password-field/password-field-visibility-trigger.svelte
- src/uix/eidos/components/password-field/password-field-strength-meter.svelte
- src/uix/eidos/components/textarea/textarea-count.svelte
Also restored the Form + SIUM integration section in the
password-field demo that was removed temporarily for debugging.
Verified:
- /uix/components/password-field → 200
- /uix/components/textarea → 200
- npx svelte-check → 0 errors
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
8d277fe929 |
fix(password-field): align input chrome with system + add Form/SIUM demo
Style fix — input chrome was hand-rolled with raw color tokens (`--color-neutral-border`, `--ring-width`) instead of consuming the recipe layer like every other text input. Now matches search-field and number-field exactly: - Added `password-field` recipe to `src/uix/eidos/lib/recipes/base.ts` with the same surface as `search-field` (height, px, gap, font-size per size; border, focus, invalid, bg, color, focus-shadow tokens). - Regenerated `src/uix/eidos/generated/base.css`. - Rewrote `password-field.css` to consume the new tokens — same focus ring (`focus-shadow`), same border colors, same disabled opacity, same transition tokens. VisibilityTrigger gets its own border/bg/ color-on-hover scheme aligned with search-field's clear button. - Added per-size icon + caps font-size tokens so the trigger and caps pill scale with the input. Form + SIUM demo — answers "how does PasswordField validate?": Added a new subsection in the Live tab with three working forms wired through `<Form>` + `<Field>` and SIUM schemas: 1. Login (purpose='current', non-empty password) 2. Registration (purpose='new', complexity rules via regex pipes — uppercase + digit + symbol + 8+ chars, plus the visual StrengthMeter as a companion to the schema) 3. Password change (3 fields with a cross-field `refine()` so new === confirm; demonstrates SIUM's refine pattern) The section explicitly documents that PasswordField has no built-in complexity rules — validation is schema-first. The schema decides what's "correct"; PasswordField's flags propagate via Field's OR-merge of `formFieldState.isInvalid`. svelte-check: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
5 months ago |
|
|
da3e5026bb |
feat: build PasswordField (full stack)
Dedicated password input component — matches the Ark UI / Chakra v3 /
Mantine / PrimeReact / Ant Design pattern of treating PasswordField as
its own first-class component (not a TextField variant). Includes one
feature that NO surveyed library ships: a CapsLock indicator driven by
KeyboardEvent.getModifierState('CapsLock').
Anatomy (5 parts, 4 events):
- Provider (root, value + visibility state)
- Input (the textbox; type flips between password / text)
- VisibilityTrigger (eye toggle button; aria-pressed + aria-controls)
- CapsLockIndicator (status live region; data-active toggles via soma
listening to keydown/keyup on Input)
- StrengthMeter (composable; consumer provides 0-4 score; UIX bundles
no zxcvbn-style algorithm — dependency-free by design)
Events:
- commit-input (every keystroke)
- commit-submit (Enter)
- shift-toggle-visibility (eye click)
- shift-caps-state (Caps Lock state flips while focused)
Key behaviors:
- `purpose: 'current' | 'new'` drives native autocomplete attribute
('current-password' for login / 'new-password' for registration).
- Field integration: disabled/readonly/required/invalid OR-merged.
- VisibilityTrigger gets tabindex=-1 so it doesn't break the form's
natural tab order — mouse / programmatic focus only, matching Ark UI.
- Caps Lock indicator is aria-live="polite" — announces on activation
while focused, silent when inactive.
- Letter-spacing increased on the input for legibility while masked.
Reference library survey performed (10 libraries) before implementation —
all consensus around dedicated component, all have built-in toggle,
ZERO have caps lock warning. Documented in the demo's API tab with a
feature comparison table.
Demo follows DEMO_AUTHORING_GUIDE canonical 6-tab depth: Live (controls
grouped by layer + code snippets at the bottom), API (props + reference
comparison), Morfo (field/value + Parts + Events tables), Sema (events
table with ▶ play), Recipe (selectors with morfo/eidos owner tags), A11y
(concern/contract table).
Also includes external work in words/ from a separate session
(unrelated to PasswordField) — not touching Words is the standing rule,
but the diff was already in the working tree.
svelte-check: 0 errors in PasswordField files (Words errors are tracked
by its own development branch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |
|
|
940e94bb64 |
Build TextArea component + fix locale propagation bug across soma providers
Major work in this session:
1. NumberField demo refactored to expose Intl.NumberFormatOptions live
- 9 canonical presets (currency/unit/percent/compact/plain)
- Locale/currency/unit lists derived from $libs/langs, $libs/currency,
$format/units (no more hardcoded arrays)
- Default locale from uix.format.numbers.getLocale()
2. Bug fix: locale prop ignored when soma.format service is active
- soma/NumberField: 5 bypass points via new `hasOwnLocale` getter
(decimalSeparator, groupSeparator, formatValue, formatEditValue, parseValue)
- soma/Calendar: dateOrder respects locale prop (matches DateField precedence)
- soma/RangeCalendar: same dateOrder fix
- When the consumer passes `locale` prop, soma.nums/dates is bypassed
and Intl is used directly with the prop locale
3. TextArea — new component, full 4-layer stack
- morfo: 3 parts (provider/input/count), 3 events
(commit-input/commit-submit/shift-count-overflow), APG textbox
- soma: TextAreaProvider with autosize (scrollHeight measurement),
submitOn modes (false|enter|shift+enter|mod+enter), maxLength soft clamp,
Field integration (OR-merge), counter overflow tracking
- eidos: 5 sizes × 3 variants × 8 colors, autosize suppresses CSS resize
- sema: commit-submit + shift-count-overflow perceptual cues
- demo: canonical 6-tab depth, all props exposed as live controls
- PrimitiveTextareaAttributes added to soma/types/html.ts
4. Also includes accumulated unreleased work from prior sessions:
- Eidos ports: announce, clipboard, drag-drop, feed, table,
tree-grid, tree-view, virtual-grid, virtual-list, words
- Their canonical demos under web/routes/uix/components/
- Semantic events defined for utility morfos
(announce/clipboard/virtual-list/virtual-grid)
- Tree indent guides fixed (TreeView ::before, TreeGrid stacked gradients)
- Sidebar reorganized with Data + Utilities groups
- Eidos words content layer and primitives
Reference library survey performed before TextArea API design (Mantine,
Radix Themes, Chakra v3, React Aria, Ark UI, Bits UI, shadcn, MUI, Prime).
Decision documented: no separate CurrencyField/UnitField — single
NumberField with formatOptions is the canon (matches all surveyed
headless libs except PrimeReact's mode enum, which still uses 1 component).
svelte-check: 0 errors in modified/created files.
See CONTINUE.md for full session notes and next steps.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
5 months ago |