Skip to content

fix(ui): dark-mode skeleton shimmer and border parity#49

Merged
Jesssullivan merged 1 commit intomainfrom
fix/skeleton-dark-mode
Apr 16, 2026
Merged

fix(ui): dark-mode skeleton shimmer and border parity#49
Jesssullivan merged 1 commit intomainfrom
fix/skeleton-dark-mode

Conversation

@Jesssullivan
Copy link
Copy Markdown
Owner

Summary

  • Replace hardcoded hex CSS with light-dark() in 8 components
  • Skeleton loading shimmer now renders correctly in dark mode
  • Border colors use light-dark(var(--color-surface-300), var(--color-surface-700))
  • Scrollbar tracks and thumbs follow host theme

Components

ClientForm, DateTimePicker, PaymentSelector, ProviderPicker, ServicePicker, StripeCheckout, VenmoButton, VenmoCheckout

Test plan

  • pnpm test — 583/583 pass
  • Visual QA in dark mode host app

Replace hardcoded hex CSS with light-dark() for 8 components:
skeleton loading shimmer, border colors, scrollbar tracks.
Ensures proper dark-mode rendering when consumed by host apps.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 16, 2026

Greptile Summary

This PR converts hardcoded light-mode CSS variable references to light-dark() across 8 components, fixing skeleton shimmer visibility and border contrast in dark-mode host apps. The border changes (light-dark(var(...), var(...))) are straightforwardly valid; the gradient-wrapped shimmer declarations are supported in modern browsers but silently drop the background in older engines that lack light-dark() support, leaving skeleton placeholders invisible rather than degrading to a light-mode fallback.

Confidence Score: 5/5

Safe to merge — the only finding is a P2 progressively-enhanced fallback suggestion with no functional regression on supported browsers.

All changes are CSS-only and internally consistent. The sole concern is the absence of a plain-gradient fallback before the light-dark() declarations, which is a best-practice suggestion rather than a blocking defect. No browser that already supports light-dark() (the intended target) is broken.

No files require special attention; the fallback suggestion applies equally to all six skeleton-shimmer components.

Important Files Changed

Filename Overview
src/components/DateTimePicker.svelte Skeleton shimmer and spinner border updated to use light-dark(); gradient fallback missing for unsupported browsers
src/components/ClientForm.svelte Input/textarea border color converted to light-dark() with valid color values — straightforward and correct
src/components/StripeCheckout.svelte Skeleton element background converted to light-dark(); same no-fallback concern as other skeleton components
src/components/VenmoCheckout.svelte Skeleton button and spinner border converted to light-dark(); same no-fallback concern
src/components/PaymentSelector.svelte Skeleton shimmer background converted to light-dark(); same no-fallback concern
src/components/ProviderPicker.svelte Skeleton avatar/text/provider backgrounds converted to light-dark(); same no-fallback concern
src/components/ServicePicker.svelte Skeleton card background converted to light-dark(); same no-fallback concern
src/components/VenmoButton.svelte Skeleton button background converted to light-dark(); same no-fallback concern

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Host App sets color-scheme] --> B{Browser supports light-dark}
    B -- Yes - modern --> C[light-dark resolves correctly]
    C --> D[Light mode - surface-200/300 gradient shimmer]
    C --> E[Dark mode - surface-700/800 gradient shimmer]
    B -- No - older browser --> F[Declaration silently ignored]
    F --> G[Skeleton background = transparent - no shimmer visible]
Loading

Reviews (1): Last reviewed commit: "fix(ui): dark-mode skeleton shimmer and ..." | Re-trigger Greptile

Comment on lines 254 to 260
.skeleton-slot {
background: linear-gradient(90deg, var(--color-surface-200) 25%, var(--color-surface-300) 50%, var(--color-surface-200) 75%);
background: light-dark(
linear-gradient(90deg, var(--color-surface-200) 25%, var(--color-surface-300) 50%, var(--color-surface-200) 75%),
linear-gradient(90deg, var(--color-surface-800) 25%, var(--color-surface-700) 50%, var(--color-surface-800) 75%)
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing fallback for unsupported browsers

light-dark() with linear-gradient() arguments is only supported in Chrome 123+, Firefox 120+, and Safari 17.5+. In browsers without support the entire background declaration is silently dropped, leaving skeleton shimmer elements with a transparent background — the loading placeholder becomes invisible. Adding a plain gradient before the light-dark() declaration lets unsupported browsers degrade gracefully to the light-mode shimmer.

Suggested change
.skeleton-slot {
background: linear-gradient(90deg, var(--color-surface-200) 25%, var(--color-surface-300) 50%, var(--color-surface-200) 75%);
background: light-dark(
linear-gradient(90deg, var(--color-surface-200) 25%, var(--color-surface-300) 50%, var(--color-surface-200) 75%),
linear-gradient(90deg, var(--color-surface-800) 25%, var(--color-surface-700) 50%, var(--color-surface-800) 75%)
);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
background: linear-gradient(90deg, var(--color-surface-200) 25%, var(--color-surface-300) 50%, var(--color-surface-200) 75%);
background: light-dark(
linear-gradient(90deg, var(--color-surface-200) 25%, var(--color-surface-300) 50%, var(--color-surface-200) 75%),
linear-gradient(90deg, var(--color-surface-800) 25%, var(--color-surface-700) 50%, var(--color-surface-800) 75%)
);

The same pattern applies to the .skeleton-* rules in PaymentSelector, ProviderPicker, ServicePicker, StripeCheckout, VenmoButton, and VenmoCheckout.

@Jesssullivan Jesssullivan merged commit 15779a5 into main Apr 16, 2026
4 checks passed
@Jesssullivan Jesssullivan deleted the fix/skeleton-dark-mode branch April 16, 2026 12:16
Jesssullivan added a commit to tinyland-inc/scheduling-kit that referenced this pull request Apr 16, 2026
* refactor!: remove middleware code (belongs in acuity-middleware) (#10)

Removed: src/middleware/ (33 files), modal-app.py, Dockerfile,
live tests, playwright deps. Version 0.3.1 to 0.4.0.

* chore: bump version to 0.5.0

* refactor!: remove acuity-scraper adapter

Scraper belongs in acuity-middleware, not the scheduling library.
Deprecated since extract-business.ts + middleware wizard steps
replaced all scraper functionality.

BREAKING: AcuityScraper, createScraperAdapter, scrapeServicesOnce,
scrapeAvailabilityOnce removed from @tummycrypt/scheduling-kit/adapters.

* build: add Bazel 8 configuration with subpackage targets

- MODULE.bazel: bzlmod config with rules_js 2.9.1, rules_ts 3.8.4, SWC, pnpm 9
- BUILD.bazel: svelte-package build, npm_package, 6 subpackage ts_project
  targets (core, adapters, payments, reconciliation, lib, testing), vitest,
  svelte-check typecheck
- .bazelrc: build/CI/debug/release configs with disk cache
- .bazelversion: pin to 8.1.1
- .npmrc: hoist=false (required by rules_js)

* feat: v0.5.0 - remove acuity-scraper, add Bazel 8 config (#11)

* chore: bump version to 0.5.0

* refactor!: remove acuity-scraper adapter

Scraper belongs in acuity-middleware, not the scheduling library.
Deprecated since extract-business.ts + middleware wizard steps
replaced all scraper functionality.

BREAKING: AcuityScraper, createScraperAdapter, scrapeServicesOnce,
scrapeAvailabilityOnce removed from @tummycrypt/scheduling-kit/adapters.

* build: add Bazel 8 configuration with subpackage targets

- MODULE.bazel: bzlmod config with rules_js 2.9.1, rules_ts 3.8.4, SWC, pnpm 9
- BUILD.bazel: svelte-package build, npm_package, 6 subpackage ts_project
  targets (core, adapters, payments, reconciliation, lib, testing), vitest,
  svelte-check typecheck
- .bazelrc: build/CI/debug/release configs with disk cache
- .bazelversion: pin to 8.1.1
- .npmrc: hoist=false (required by rules_js)

* feat(venmo): add payeeEmail option to route payments to practitioner

When payeeEmail is set in VenmoAdapterConfig, the PayPal order
creation includes payee.email_address in purchase_units. This
routes payments directly to the practitioner's PayPal account
without requiring their API credentials.

Ref: PayPal "Pay another account" docs

* chore: bump version to 0.5.1 (payee-email support)

* fix(ci): use @Jesssullivan scope for GitHub Packages mirror (Jesssullivan#18)

* feat(venmo): add returnUrl/cancelUrl to experience_context (Jesssullivan#19)

* fix(ci): use @Jesssullivan scope for GitHub Packages mirror

* feat(venmo): add returnUrl/cancelUrl to experience_context

PayPal requires return_url and cancel_url in the Venmo payment source
experience_context for proper popup handling. Without them, PayPal may
force additional buyer verification loops or block the popup flow.

New optional fields on VenmoAdapterConfig: returnUrl, cancelUrl.

* chore: bump to 0.5.2 (PayPal return URLs) (Jesssullivan#20)

* fix(ci): use @Jesssullivan scope for GitHub Packages mirror

* feat(venmo): add returnUrl/cancelUrl to experience_context

PayPal requires return_url and cancel_url in the Venmo payment source
experience_context for proper popup handling. Without them, PayPal may
force additional buyer verification loops or block the popup flow.

New optional fields on VenmoAdapterConfig: returnUrl, cancelUrl.

* chore: bump to 0.5.2 (PayPal return URLs)

* feat: onboarding subpackage — provider credential management (Jesssullivan#21-Jesssullivan#27) (Jesssullivan#28)

New @tummycrypt/scheduling-kit/onboarding subpackage:

Interfaces:
- CredentialStore: app-provided key-value storage (PG, Redis, etc.)
- EncryptionProvider: app-provided encryption (AES, Vault, etc.)
- StripeConnectConfig, StripeAccountStatus, WebhookSetupResult types

Stripe:
- buildStripeAuthorizeUrl() + exchangeStripeCode() — Connect OAuth
- getStripeAccountStatus() — account onboarding status
- validateStripeKeys() — key validation against Stripe API
- createStripeWebhook() + deleteStripeWebhooks() — webhook CRUD

PayPal:
- validatePayPalCredentials() — OAuth token validation
- createPayPalWebhook() — webhook creation

Build:
- Bazel //src/onboarding target (deps: :core, :payments, effect)
- Package.json ./onboarding export

Pattern: library defines interfaces + helpers, application provides
CredentialStore implementation. Same pattern as HomegrownAdapter's
getDb callback — scheduling-kit doesn't know about databases.

Closes Jesssullivan#21, Jesssullivan#22, Jesssullivan#23, Jesssullivan#24, Jesssullivan#27. Partial Jesssullivan#25, Jesssullivan#26.

* chore: bump to 0.6.0 (onboarding subpackage) (Jesssullivan#29)

* feat: adapter factory pattern + 21 onboarding tests (Jesssullivan#25, Jesssullivan#26) (Jesssullivan#30)

- createAdapterFactory(): settings-driven singleton with cache,
  promise dedup, reset, and disable lifecycle
- 21 tests: Stripe OAuth URL, key validation, account status,
  PayPal credential validation, factory lifecycle (cache, reset,
  disable, store passthrough)
- Updated vitest.config.ts to include onboarding test glob

Closes Jesssullivan#25, Jesssullivan#26.

* chore: strip sourcemaps from npm package (Jesssullivan#31)

* feat: adapter factory pattern + 21 onboarding tests (Jesssullivan#25, Jesssullivan#26)

- createAdapterFactory(): settings-driven singleton with cache,
  promise dedup, reset, and disable lifecycle
- 21 tests: Stripe OAuth URL, key validation, account status,
  PayPal credential validation, factory lifecycle (cache, reset,
  disable, store passthrough)
- Updated vitest.config.ts to include onboarding test glob

Closes Jesssullivan#25, Jesssullivan#26.

* chore: strip sourcemaps from npm package (2,711 .map files excluded)

* feat: provider status helpers + SetupStep type (Jesssullivan#32) (Jesssullivan#33)

* chore: bump to 0.6.1 (status helpers) (Jesssullivan#34)

* align build truth and package boundaries (Jesssullivan#39)

* ci: enforce Bazel release metadata truth (Jesssullivan#40)

* docs: add agent and llm operating brief (Jesssullivan#42)

* feat(payments)!: converge PaymentCapabilities contract from tinyland-inc (Jesssullivan#45)

* feat(payments)!: converge PaymentCapabilities contract from tinyland-inc

Cherry-pick tinyland-inc/main squash (v0.7.0) onto Jesssullivan/main.
Keeps Jess's CI/publish workflows and Bazel structure.
Bumps all version references to 0.7.0.

- PaymentCapabilities, StripeCapability, VenmoCapability types
- getDefaultCapabilities() factory
- HybridCheckoutDrawer: capabilities prop replaces individual payment props
- Cash at Visit structurally removed (cash: false)

* fix(ci): skip prepublish scripts in gh packages mirror

* docs(release): clarify scheduling-kit authority (Jesssullivan#46)

* ci(publish): validate bazel package artifact (Jesssullivan#47)

* build(bazel): publish scheduling-kit from bazel artifact

* fix(ui): dark-mode skeleton shimmer and border parity (Jesssullivan#49)

Replace hardcoded hex CSS with light-dark() for 8 components:
skeleton loading shimmer, border colors, scrollbar tracks.
Ensures proper dark-mode rendering when consumed by host apps.

* ci: support honey self-hosted runner stopgap (Jesssullivan#50)

* ci: isolate pnpm store on self-hosted runners (Jesssullivan#51)

* fix: make publish workflow self-hosted-safe (Jesssullivan#52)

* fix(ci): make github package artifact writable (Jesssullivan#53)

* perf(components): drop zod from browser client form (Jesssullivan#54)

* fix(ci): ignore npm scripts when publishing bazel pkg (Jesssullivan#55)

* fix(ci): ignore npm scripts when publishing bazel pkg

* fix(ci): clean stale bazel publish artifacts on runners

* fix(ci): partition pnpm caches by runner (Jesssullivan#56)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant