Commit Graph

923 Commits

Author SHA1 Message Date
ilia db6384110e Merge pull request 'Feature/popover' (#48) from feature/popover into main
Workflow / build (push) Failing after 3m11s
Workflow / e2e (push) Has been skipped
Workflow / publish (push) Has been skipped
Reviewed-on: #48
2026-06-02 18:47:17 +00:00
Ilia Mashkov cbd95350bb fix(popover): stop animating left/top so first open doesn't slide from corner
Workflow / build (pull_request) Successful in 1m18s
Workflow / e2e (pull_request) Successful in 1m15s
Workflow / publish (pull_request) Has been skipped
2026-06-02 21:38:48 +03:00
Ilia Mashkov a8a985ee6a chore: remove bits-ui dependency 2026-06-02 17:08:58 +03:00
Ilia Mashkov be073286dc refactor(typography-menu): use native Popover instead of bits-ui 2026-06-02 16:28:05 +03:00
Ilia Mashkov 7798c4bbdf refactor(combo-control): use native Popover instead of bits-ui
The native Popover always renders its content (the vertical slider), so the
slider's value label is in the DOM even when closed, and opening is driven by
the browser's declarative popovertarget invoker (not simulated by jsdom on
click). Update the tests to scope value assertions to the trigger and drive
open via showPopover(), matching Popover.svelte.test.ts.
2026-06-02 16:21:32 +03:00
Ilia Mashkov 3ae22ad515 docs(popover): add storybook stories 2026-06-02 16:16:28 +03:00
Ilia Mashkov ffa897ee54 test(popover): cover open/close state and aria wiring 2026-06-02 16:13:40 +03:00
Ilia Mashkov 93c52dd132 fix(popover): gate visibility until positioned, tighten types 2026-06-02 16:12:11 +03:00
Ilia Mashkov 9e0c8f740b feat(popover): native Popover API component with anchored positioning 2026-06-02 16:06:20 +03:00
Ilia Mashkov b1b5177e02 test: add jsdom Popover API shim 2026-06-02 16:03:54 +03:00
Ilia Mashkov ef9cd33e48 feat(popover): add pure anchored-positioning math 2026-06-02 15:59:58 +03:00
ilia f3c76df2c5 Merge pull request 'Feature/slider' (#47) from feature/slider into main
Workflow / build (push) Successful in 1m24s
Workflow / e2e (push) Successful in 1m11s
Workflow / publish (push) Successful in 15s
Reviewed-on: #47
2026-06-02 12:10:42 +00:00
Ilia Mashkov ae2d0e3c2f fix(slider): focus thumb on pointerdown for keyboard parity
Workflow / build (pull_request) Successful in 1m33s
Workflow / e2e (pull_request) Successful in 1m21s
Workflow / publish (pull_request) Has been skipped
2026-06-02 11:14:10 +03:00
Ilia Mashkov 3f5151efa0 docs(slider): update story copy for native implementation 2026-06-02 11:08:58 +03:00
Ilia Mashkov 19d9b07c55 test(slider): centralize jsdom pointer shims and add vertical drag test 2026-06-02 11:08:07 +03:00
Ilia Mashkov 1209358d40 test(slider): cover keyboard and pointer interaction 2026-06-02 11:02:52 +03:00
Ilia Mashkov d7decd7a00 fix(slider): normalize value, reactive trackEl, aria-valuetext 2026-06-02 11:01:08 +03:00
Ilia Mashkov 9d6220d2ec feat(slider): reimplement natively without bits-ui 2026-06-02 10:54:54 +03:00
Ilia Mashkov 4756682863 feat(slider): add pure value/position math helpers 2026-06-02 10:50:46 +03:00
ilia 67af3d946a Merge pull request 'chore: introduce font files caching and compressing' (#46) from chore/font-files-caching into main
Workflow / build (push) Successful in 1m19s
Workflow / e2e (push) Successful in 1m8s
Workflow / publish (push) Successful in 14s
Reviewed-on: #46
2026-06-01 07:42:51 +00:00
Ilia Mashkov c6d0270072 chore: introduce font files caching and compressing
Workflow / build (pull_request) Successful in 1m27s
Workflow / e2e (pull_request) Successful in 1m19s
Workflow / publish (pull_request) Has been skipped
2026-06-01 10:37:50 +03:00
ilia f7cd6b5081 Merge pull request 'Feature/local fonts' (#45) from feature/local-fonts into main
Workflow / build (push) Successful in 1m20s
Workflow / e2e (push) Successful in 1m7s
Workflow / publish (push) Successful in 28s
Reviewed-on: #45
2026-06-01 07:20:21 +00:00
Ilia Mashkov dda8ef6368 docs(styles): strip decorative comment banners from app.css
Workflow / build (pull_request) Successful in 1m48s
Workflow / e2e (pull_request) Successful in 1m17s
Workflow / publish (pull_request) Has been skipped
Replace ASCII-art separators (====, box-drawing rules, ---- dashes) with
plain section labels and rewrite the casual one-liners as terse, factual
comments.
2026-06-01 10:11:42 +03:00
Ilia Mashkov d77b51736a fix(styles): default body font to Inter, drop unloaded Karla
The body font-family referenced "Karla", which was never loaded, so
body text silently fell back to system-ui. Point it at the existing
--font-secondary token (Inter + system fallbacks).
2026-06-01 10:06:18 +03:00
Ilia Mashkov 1e16330097 refactor(fonts): drop Google Fonts CDN links, preload self-hosted faces
Remove the googleapis stylesheet, both google preconnects, and the two
dead fontshare preconnects from the document head. Preload the two
render-critical faces (Inter, Space Grotesk) via Vite ?url imports.
Eliminates two third-party origins and the IP leak to Google.
2026-06-01 10:06:12 +03:00
Ilia Mashkov c41016ac5d feat(fonts): self-host interface fonts as vendored latin woff2
Replace Google Fonts CDN delivery of the four UI typefaces (Inter,
Space Grotesk, Space Mono, Syne) with latin-subset woff2 vendored into
app/assets/fonts and wired via a hand-authored @font-face stylesheet.
Variable faces keep wght (Inter also opsz). Vite content-hashes the
binaries for immutable caching.
2026-06-01 10:06:07 +03:00
Ilia Mashkov aa4189f6a8 chore(app): declare *.woff2?url module type for asset imports 2026-06-01 10:05:33 +03:00
ilia eb5a8d1e5b Merge pull request 'Fixes/request deduplication' (#44) from fixes/request-deduplication into main
Workflow / build (push) Successful in 1m28s
Workflow / e2e (push) Successful in 1m14s
Workflow / publish (push) Successful in 15s
Reviewed-on: #44
2026-05-28 19:54:57 +00:00
Ilia Mashkov e698dc6e07 docs: remove GIT_WORKFLOW.md
Workflow / build (pull_request) Successful in 1m35s
Workflow / e2e (pull_request) Successful in 1m22s
Workflow / publish (pull_request) Has been skipped
2026-05-28 22:29:02 +03:00
Ilia Mashkov 5d72bb7a4c refactor(fontCatalogStore): single source of truth for query params
On initial load, two separate $effects in bindings.svelte.ts — one for
filters, one for sort — each issued its own setOptions with a different
queryKey on the first flush, producing an orphaned
`/fonts?limit=50&offset=0` request immediately followed by the real
`/fonts?limit=50&sort=popularity&offset=0`. Hardcoding the default sort
on the singleton would have papered over the symptom while leaving the
sortStore default and the catalog-store default coupled by hand.

Make bindings the sole emitter of query params:

- features/.../bindings: merge filter + sort effects into one. The effect
  reads both stores, builds the merged param object, and issues a single
  setParams. No more interleaved setOptions on mount.
- entities/.../fontCatalogStore: gate the observer with `enabled: false`
  on construction. The first setParams flips `#enabled` on and triggers
  exactly one fetch with the correct queryKey. Removes the need for a
  hardcoded default sort on the singleton.
- isEmpty is also gated on `#enabled` so the brief pre-config window
  doesn't render "no results" before bindings configures the query.
- The constructor seeds #result from observer.getCurrentResult() because
  subscribe may not fire synchronously when the observer is disabled.
2026-05-28 21:38:17 +03:00
Ilia Mashkov 7f20f36d0a fix(api): mark schema-validation errors as non-retryable
The proxy returned `{fonts: null, total: 0}` for empty results, which
fetchProxyFonts surfaced as a generic Error. fontCatalogStore wrapped it
as FontNetworkError, and TanStack retried 3× with exponential backoff —
pinning the loading skeleton for ~7s before settling on an empty list.

Schema mismatches are deterministic; retrying only delays surfacing the
contract violation.

- shared/api/queryClient: introduce NonRetryableError marker class.
  The default retry handler short-circuits when it sees this so any
  store using the shared client gets fail-fast behavior for free.
- entities/Font/lib/errors: FontResponseError extends NonRetryableError.
- entities/Font/api/proxy/proxyFonts: throw FontResponseError (was a
  bare Error). Document that ProxyFontsResponse.fonts is always an array.
- entities/Font/.../fontCatalogStore.fetchPage: preserve a
  FontResponseError raised lower in the stack instead of re-wrapping
  it as FontNetworkError.
- features/FilterAndSortFonts/api/filters: throw NonRetryableError on
  invalid filters payloads and document the array-never-null contract.
2026-05-28 21:37:23 +03:00
Ilia Mashkov c90a258f6c feat(font-list): show empty state when search yields no fonts
Adds an `empty` snippet prop to FontVirtualList and supplies it from the
sidebar FontList. Settled queries with zero results now render a centered
"No typefaces found" label instead of a blank list area.
2026-05-28 21:36:23 +03:00
ilia dec83c93d0 Merge pull request 'Feature/playwright' (#43) from feature/playwright into main
Workflow / build (push) Successful in 1m28s
Workflow / e2e (push) Successful in 1m13s
Workflow / publish (push) Successful in 15s
Reviewed-on: #43
2026-05-28 13:31:16 +00:00
Ilia Mashkov b9560336d5 ci/cd: remove artifact, add build to e2e step
Workflow / build (pull_request) Successful in 1m32s
Workflow / e2e (pull_request) Successful in 2m10s
Workflow / publish (pull_request) Has been skipped
2026-05-28 16:18:25 +03:00
Ilia Mashkov 18f1d109ab fix: add single root level
Workflow / build (pull_request) Failing after 1m33s
Workflow / e2e (pull_request) Has been skipped
Workflow / publish (pull_request) Has been skipped
2026-05-28 16:11:07 +03:00
Ilia Mashkov 24f084ae77 test: add e2e suite for core comparison flows
Workflow / build (pull_request) Failing after 3m47s
Workflow / e2e (pull_request) Has been skipped
Workflow / publish (pull_request) Has been skipped
Adds 17 new specs across six files plus the supporting POM
infrastructure:

- fixtures with auto-opened comparison + typography helpers
- TypographyMenu POM reading values from ComboControl aria-labels
- ComparisonPage extended with side selection, font picking,
  slider-value reader, FontFaceSet inspection, storage snapshot
- compare-flow: A/B selection, aria-pressed state, storage write
- preview-text: input binding + slider character rendering
- slider: keyboard ARIA contract (Arrow / Shift+Arrow / Page / Home / End)
- font-loading: FontFaceSet contains selected, excludes unrelated
- persistence: font selection + typography settings survive reload
- typography: symmetric increase/decrease for all four controls
2026-05-28 15:52:40 +03:00
Ilia Mashkov b9e21a66d3 fix(comparisonStore): preserve stored selection on cold load
The seed-defaults effect fired whenever fontA/fontB were still
undefined, including the window between constructor reading storage
and the per-id batch resolving. On a slow batch or fast catalog the
effect clobbered storage with catalog[0]/catalog[N-1], losing the
user's pick on reload.

Now bails when storage already holds IDs, and reads storage via
untrack so per-font selection writes don't re-trigger the effect.

Adds a deterministic regression test that controls catalog/batch
ordering via mockImplementation timing.
2026-05-28 14:58:18 +03:00
Ilia Mashkov 7a9422b574 test: add aria attributes to tested components 2026-05-28 14:05:14 +03:00
Ilia Mashkov f79b24272c ci/cd: add e2e tests with playwright into gitea actions workflow 2026-05-28 13:20:25 +03:00
Ilia Mashkov a9229342e6 test: base playwrignt setup for firefox and chrome 2026-05-28 12:55:10 +03:00
Ilia Mashkov 05cab5f892 fix(cicd): fix yarn version, surface the real failure, bust cache
Workflow / build (push) Successful in 1m45s
Workflow / publish (push) Successful in 35s
2026-05-25 14:11:23 +03:00
Ilia Mashkov 0518c84230 fix: add .dockerignore to prevent glibc yarn cache leaking into musl build
Workflow / build (push) Successful in 1m45s
Workflow / publish (push) Failing after 9s
2026-05-25 13:57:35 +03:00
ilia 5afb9c5d5d Merge pull request 'Chore/architecture refactoring' (#42) from chore/architecture-refactoring into main
Workflow / build (push) Successful in 12s
Workflow / publish (push) Failing after 14s
Reviewed-on: #42
2026-05-25 08:43:06 +00:00
Ilia Mashkov 4126275c4d refactor(SliderArea): extract grid overlay into bg-grid utilities
Workflow / build (pull_request) Successful in 37s
Workflow / publish (pull_request) Has been skipped
The decorative dotted-grid background on the paper surface was a
6-line $derived gridStyle string applied via inline style="" plus four
extra utility classes for color and opacity. Replace with two named
utilities and let CSS handle the responsive switch.

app.css:
- New --color-grid-line CSS var (light + dark) so the grid colour and
  intensity auto-switch without consumers needing a dark: variant or an
  opacity layer.
- @utility bg-grid (20px cells) and @utility bg-grid-sm (10px cells).
  Both reference --color-grid-line, so the same markup paints correctly
  in light and dark mode.

SliderArea.svelte:
- Drop the gridStyle $derived block and the inline style= attribute.
- Overlay becomes a single line:
  <div class="absolute inset-0 pointer-events-none bg-grid-sm md:bg-grid"
       aria-hidden="true" />
  Mobile picks the tight 10px grid; the md: breakpoint flips to 20px,
  matching the prior JS-driven behaviour with no extra runtime cost.
2026-05-25 11:09:26 +03:00
Ilia Mashkov ffc28f78f5 refactor(SliderArea): bump the padding to avoid overlap with TypographyMenu 2026-05-25 11:08:04 +03:00
Ilia Mashkov 80241aa352 refactor(SliderArea): remove $derived className 2026-05-25 11:07:20 +03:00
Ilia Mashkov 37886f3aa7 refactor(TypographyMenu): use separators in one style 2026-05-25 11:01:11 +03:00
Ilia Mashkov 410a7cd37e feat(SliderArea): keyboard accessibility for the comparison slider
The slider element had role="slider" and tabindex="0" but no keyboard
handler — the focus ring appeared but the slider could not be moved.

Add a keydown handler implementing the standard ARIA slider contract:
- ArrowLeft / ArrowDown — step left by 1 percent
- ArrowRight / ArrowUp — step right by 1 percent
- Shift + arrow — coarse step (10 percent)
- PageUp / PageDown — coarse step (10 percent)
- Home — jump to 0
- End — jump to 100

Bounds and step sizes extracted as named constants (SLIDER_MIN,
SLIDER_MAX, SLIDER_STEP_FINE, SLIDER_STEP_COARSE). Position updates go
through sliderSpring.target so keyboard moves animate the same way as
pointer drags.

Also adds the missing ARIA attributes that screen readers need:
- aria-valuemin / aria-valuemax (bounds)
- aria-orientation (horizontal)
2026-05-25 10:57:54 +03:00
Ilia Mashkov b5fec3a1ba fix(SliderArea): inset paper with padding instead of scale for even gaps
scale-[0.94] shrinks proportionally — on wide viewports this produced
visibly larger horizontal gaps than vertical ones when the sidebar
opens, and it left the text engine measuring the un-scaled width
(causing the thumb-to-character morph boundary to drift).

Switch to outer-container padding (p-6 when sidebar is open on desktop)
so the paper inherits an equal pixel inset on all four sides. The
ResizeObserver picks up the new dimensions and the layout engine
re-wraps text at the actual rendered width.
2026-05-25 10:57:23 +03:00
Ilia Mashkov 8eee815e9a refactor(styles): improve light-mode contrast across surfaces and muted text
Dark mode unchanged. Targets that were reported as "barely visible" in
light theme:

Surfaces / dividers
- --color-border-subtle (light) bumped from rgb(0 0 0 / 0.05) to
  --neutral-300 (matches the Input underline variant's border color and
  yields a visible hairline on bg-surface / bg-paper).
- New bg-subtle utility (same color as border-subtle but as
  background-color) — used by Divider component and the TypographyMenu
  inline column separator. Replaces ad-hoc 'bg-black/5 dark:bg-white/10'
  and 'bg-black/10 dark:bg-white/10' bands.
- FontSearch + ComparisonView Search wrapper borders switched from
  hand-written 'border-swiss-black/5 dark:border-white/10' to
  border-subtle so they participate in the palette.

Muted text
- Button tertiary inactive text (light) bumped neutral-400 → neutral-600
  (~2.7:1 → ~7.5:1 contrast). Covers the A/B toggle and the font-list
  rows in the sidebar.
- Label/TechText muted variant (light) bumped neutral-400 → neutral-600.
  Covers the ComboControl value text.
- Link text aligned to neutral-500 / neutral-400 (subtle but visible).

No behavior changes; pure styling.
2026-05-25 10:56:51 +03:00