Merge pull request 'v2026.05.21.0 — Wear OS dispatch fix, playlist-load feedback, drift CI' (#58) from dev into main

This commit was merged in pull request #58.
This commit is contained in:
2026-05-21 15:42:06 -04:00
27 changed files with 605 additions and 507 deletions
+6 -3
View File
@@ -31,10 +31,13 @@ on:
jobs:
analyze-test-build:
# flutter-ci runner image (CI-Runner/CI-flutter/Dockerfile) bakes in
# Flutter SDK + Android cmdline-tools + platform-34 + build-tools 34.0.0
# + JDK 17 + a non-root `runner` user. No setup-action ceremony needed.
# Toolchain selected via container.image per ci-runners.md
# (label = scheduling handle, image = environment). The ci-flutter
# image ships Flutter SDK + Android cmdline-tools/platform/build-tools
# + Java 25. See ci-requirements.md.
runs-on: flutter-ci
container:
image: git.fabledsword.com/bvandeusen/ci-flutter:3.44
defaults:
run:
+2
View File
@@ -23,6 +23,8 @@ on:
jobs:
release:
runs-on: go-ci
container:
image: git.fabledsword.com/bvandeusen/ci-go:1.26
env:
IMAGE: git.fabledsword.com/bvandeusen/minstrel
+6
View File
@@ -29,6 +29,7 @@ on:
- 'sqlc.yaml'
- 'internal/**'
- 'cmd/**'
- '.golangci.yml'
- '.forgejo/workflows/test-go.yml'
pull_request:
branches: [main]
@@ -39,11 +40,14 @@ on:
- 'sqlc.yaml'
- 'internal/**'
- 'cmd/**'
- '.golangci.yml'
- '.forgejo/workflows/test-go.yml'
jobs:
test:
runs-on: go-ci
container:
image: git.fabledsword.com/bvandeusen/ci-go:1.26
steps:
- name: Checkout
@@ -65,6 +69,8 @@ jobs:
integration:
runs-on: go-ci
container:
image: git.fabledsword.com/bvandeusen/ci-go:1.26
services:
postgres:
image: postgres:16-alpine
+2 -12
View File
@@ -19,6 +19,8 @@ on:
jobs:
test:
runs-on: go-ci
container:
image: git.fabledsword.com/bvandeusen/ci-go:1.26
defaults:
run:
@@ -28,18 +30,6 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
# `cache: 'npm'` removed — the Forgejo Actions cache server at
# 172.18.0.27:41161 isn't reachable from this runner's container,
# so setup-node spent 4m41s burning the npm cache restore on
# ETIMEDOUT before failing open. npm ci still works deterministically
# from package-lock.json; just re-fetches from the registry on each
# run. Restore the cache option once the runner-host network reaches
# the cache server.
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install deps
run: npm ci
+16 -15
View File
@@ -1,28 +1,29 @@
version: "2"
run:
timeout: 5m
tests: true
linters:
disable-all: true
default: none
enable:
- errcheck
- govet
- ineffassign
- staticcheck
- unused
- revive
settings:
revive:
# Intentionally narrow: we skip `exported` (no doc-comment requirement) per
# the project's no-boilerplate-comment policy. Re-enable if the public API
# surface grows to the point where documentation lives alongside it.
rules:
- name: var-naming
- name: unused-parameter
- name: early-return
formatters:
enable:
- gofmt
- goimports
- revive
linters-settings:
revive:
# Intentionally narrow: we skip `exported` (no doc-comment requirement) per
# the project's no-boilerplate-comment policy. Re-enable if the public API
# surface grows to the point where documentation lives alongside it.
rules:
- name: var-naming
- name: unused-parameter
- name: early-return
issues:
exclude-use-default: false
+47
View File
@@ -0,0 +1,47 @@
# CI Requirements — Minstrel
> Copy of [the template](https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/requirements-sheet.template.md).
> Family-wide policy: `ci-runners.md` in the FabledRulebook.
## Runtime images
Minstrel's four workflows consume two CI images:
```
git.fabledsword.com/bvandeusen/ci-go:1.26
git.fabledsword.com/bvandeusen/ci-flutter:3.44
```
- `ci-go:1.26` — Go server tests (`.forgejo/workflows/test-go.yml`), web SPA tests (`.forgejo/workflows/test-web.yml`), and the release container build (`.forgejo/workflows/release.yml`).
- `ci-flutter:3.44` — Flutter client tests + debug/release APK builds (`.forgejo/workflows/flutter.yml`).
## Image deps used
### From `ci-go:1.26`
- **Go** (1.26 toolchain) — `go vet`, `go test -race`, `go build`, `go mod`.
- **Node + npm** — `npm ci` and `npm test` / `npm run check` in `test-web.yml`.
- **golangci-lint** — lint pass in `test-go.yml`.
- **docker CLI** — bridge-IP discovery of the per-job Postgres service container in `test-go.yml` integration job (via the runner's shared `/var/run/docker.sock`).
- **docker buildx** — release container build + push in `release.yml`.
- **curl** — release-asset polling / upload in `release.yml`.
### From `ci-flutter:3.44`
- **Flutter** (3.44 stable channel) — `flutter pub get`, `flutter analyze --fatal-infos`, `flutter test`, `flutter build apk` (debug + signed release).
- **Dart** — `dart run tool/gen_tokens.dart`, `dart run build_runner build` (drift codegen).
- **Android SDK + NDK + cmdline-tools + build-tools** — APK assembly + signing.
- **Java 25** — Gradle / Android build.
- **git** — `actions/checkout@v4` baseline (and any shell git operations).
- **base64 + curl** — keystore decode + release-asset upload in the tag-build path.
## Per-job tool installs
None.
## Notes
- **Label/image split.** Workflows keep `runs-on: go-ci` / `runs-on: flutter-ci` as the scheduling label per the [`ci-runners.md`](https://…/FabledRulebook/ci-runners.md) "label = scheduling handle, image = `container.image`" pattern. The labels are intentional handles, not toolchain assertions.
- **Integration-job docker-socket dependency.** `test-go.yml`'s integration job uses the runner's shared docker socket (`/var/run/docker.sock`) to bridge-IP-discover the per-job Postgres service container by name + network intersection — the dev compose's `minstrel-postgres-*` containers are explicitly skipped as belt-and-suspenders. Depends on `act_runner.valid_volumes` whitelisting the socket; if that ever stops auto-mounting, integration tests fail at the `docker inspect` step.
- **Go toolchain pin.** `go.mod` is on `go 1.25.0` because `golang.org/x/crypto v0.51.0` declares 1.25 as its minimum. `ci-go:1.26` satisfies this with headroom. Future `x/crypto` bumps that move the Go floor should be paired with an image-tag bump in this file + the workflows.
- **In-app update channel polling.** `release.yml` polls Forgejo's release-asset API for up to 15 min on tag pushes to fetch the APK that `flutter.yml` is concurrently attaching to the same release. The asset eventually appears because `flutter.yml` and `release.yml` run in parallel on the same tag; if the polling times out, the server image ships without the bundled update channel (graceful degradation, not a build failure).
- **Cache server reachability.** `test-web.yml` does NOT use `cache: 'npm'` on `actions/setup-node` — the Forgejo Actions cache server isn't reachable from this runner's container network and `setup-node` was burning ~4m41s on ETIMEDOUT before failing open. With the migration to `ci-go:1.26`, `setup-node` is removed entirely (Node is in the image). The cache concern reappears if a future change re-introduces a network-dependent action.
- **Friction asks.** None pending. The two images cover everything Minstrel needs.
+31 -13
View File
@@ -559,13 +559,24 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
mediaItem.add(current.copyWith(artUri: Uri.file(path)));
}
/// Drives the session to a terminal state and tears down the
/// foreground service so external surfaces (Wear tile, lock screen,
/// in-app mini bar) drop it instead of showing a stale paused track.
/// Called on the idle timeout and from onTaskRemoved when not playing;
/// no other path stops the session (the notification is ongoing).
@override
Future<void> stop() async {
/// Display-state-only teardown. Stops playback, clears the queue and
/// current track, and broadcasts idle so external surfaces (Wear tile,
/// notification, in-app mini bar) drop their visible state — but does
/// NOT call super.stop(), so the FGS and MediaSessionCompat remain
/// alive and addressable.
///
/// Why this matters (Fable #472): the paired Wear OS companion app
/// caches a MediaController bound to our MediaSession's token. If
/// the system destroys our service (super.stop() → stopSelf() makes
/// it eligible under memory pressure), the next play() spins up a
/// new MediaSession with a fresh token; the companion's cached
/// controller still points at the dead one and transport taps from
/// notification + watch silently no-op. Keeping the service alive
/// across idle periods preserves the binding.
///
/// Used by the #52 idle timeout. Full termination (super.stop) is
/// reserved for the explicit-close path (onTaskRemoved while idle).
Future<void> _softTeardown() async {
_idleStopTimer?.cancel();
_idleStopTimer = null;
_positionBroadcastTimer?.cancel();
@@ -575,17 +586,22 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
try {
await _player.stop();
} catch (_) {}
// Explicit terminal broadcast before super.stop() tears down the
// isolate/notification, so subscribers see idle even if the
// player's own event lags the service teardown. queue/mediaItem
// cleared so the in-app mini bar collapses in lockstep with the
// watch tile rather than lingering on the last track.
playbackState.add(playbackState.value.copyWith(
playing: false,
processingState: AudioProcessingState.idle,
));
mediaItem.add(null);
queue.add([]);
}
/// Full termination — soft teardown plus super.stop(), which calls
/// stopSelf() on the AudioService and lets the OS reclaim it. Reserved
/// for the explicit-close path (onTaskRemoved when not playing). On the
/// idle path we use _softTeardown instead to preserve the Wear OS
/// MediaController binding (Fable #472).
@override
Future<void> stop() async {
await _softTeardown();
await super.stop();
}
@@ -817,7 +833,9 @@ class MinstrelAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandl
if (_player.playing && ps != ProcessingState.completed) {
return; // resumed between arm and fire
}
unawaited(stop());
// _softTeardown — not full stop() — to preserve the MediaSession
// binding for the paired Wear OS companion (Fable #472).
unawaited(_softTeardown());
}
/// While actively playing, keeps a 1s periodic PlaybackState re-
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -70,7 +72,7 @@ class PlaylistCard extends ConsumerWidget {
right: 6,
child: PlayCircleButton(
enabled: hasTracks,
onPressed: () => _playPlaylist(ref),
onPressed: () => _playPlaylist(context, ref),
),
),
// System playlists get a refresh affordance so the
@@ -187,16 +189,34 @@ class PlaylistCard extends ConsumerWidget {
/// PlaylistTrack into a TrackRef (filtering out unavailable rows
/// whose `trackId` is null after a track-delete), and plays from
/// index 0. Mirrors the web PlaylistCard's onPlayClick.
Future<void> _playPlaylist(WidgetRef ref) async {
///
/// Failure modes are surfaced via SnackBar so a not-yet-built mix
/// or a slow server doesn't look like the tap was ignored.
Future<void> _playPlaylist(BuildContext context, WidgetRef ref) async {
final messenger = ScaffoldMessenger.of(context);
final api = await ref.read(playlistsApiProvider.future);
// Refreshable (singleton) system playlists: fetch the server's
// rotation-aware order (#415) and play as-is, tagged with the
// source so the reporter advances rotation. User playlists AND
// non-singleton system kinds (songs_like_artist — no by-kind
// endpoint) play the stored order via get(), untagged.
final detail = playlist.refreshable
? await api.systemShuffle(playlist.systemVariant!)
: await api.get(playlist.id);
final PlaylistDetail detail;
try {
// Refreshable (singleton) system playlists: fetch the server's
// rotation-aware order (#415) and play as-is, tagged with the
// source so the reporter advances rotation. User playlists AND
// non-singleton system kinds (songs_like_artist — no by-kind
// endpoint) play the stored order via get(), untagged.
detail = await (playlist.refreshable
? api.systemShuffle(playlist.systemVariant!)
: api.get(playlist.id))
.timeout(const Duration(seconds: 8));
} on TimeoutException {
messenger.showSnackBar(const SnackBar(
content: Text("Couldn't load playlist — check your connection"),
));
return;
} catch (e) {
messenger.showSnackBar(SnackBar(
content: Text('Playlist load failed: $e'),
));
return;
}
final refs = <TrackRef>[];
for (final t in detail.tracks) {
if (t.trackId == null) continue;
@@ -211,7 +231,12 @@ class PlaylistCard extends ConsumerWidget {
streamUrl: t.streamUrl ?? '',
));
}
if (refs.isEmpty) return;
if (refs.isEmpty) {
messenger.showSnackBar(const SnackBar(
content: Text("Mix isn't ready yet — try again in a moment"),
));
return;
}
// Rotation-aware kinds arrive pre-ordered from the server — play
// as-is, tagged so the reporter advances rotation. No client
// shuffle. Everything else plays stored order, untagged.
@@ -69,18 +69,24 @@ class AddToPlaylistSheet extends ConsumerWidget {
itemCount: owned.length,
itemBuilder: (_, i) {
final p = owned[i];
return ListTile(
key: Key('add_to_playlist_${p.id}'),
leading: Icon(LucideIcons.list_music, color: fs.parchment),
title: Text(
p.name,
style: TextStyle(color: fs.parchment),
// Material(transparency) gives ListTile an ink target
// beneath the outer Container's color paint — required
// by the Flutter 3.44 ListTile/ColoredBox assertion.
return Material(
type: MaterialType.transparency,
child: ListTile(
key: Key('add_to_playlist_${p.id}'),
leading: Icon(LucideIcons.list_music, color: fs.parchment),
title: Text(
p.name,
style: TextStyle(color: fs.parchment),
),
subtitle: Text(
'${p.trackCount} ${p.trackCount == 1 ? "track" : "tracks"}',
style: TextStyle(color: fs.ash, fontSize: 12),
),
onTap: () => Navigator.pop(context, p.id),
),
subtitle: Text(
'${p.trackCount} ${p.trackCount == 1 ? "track" : "tracks"}',
style: TextStyle(color: fs.ash, fontSize: 12),
),
onTap: () => Navigator.pop(context, p.id),
);
},
);
@@ -236,10 +236,17 @@ class _MenuItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
return ListTile(
leading: Icon(icon, color: fs.parchment),
title: Text(label, style: TextStyle(color: fs.parchment)),
onTap: onTap,
// Transparency-typed Material sits between the outer Container's color
// paint and the ListTile so ListTile can paint its own ink splashes.
// Flutter 3.44 promoted the "ListTile inside ColoredBox without Material"
// warning to a hard assertion.
return Material(
type: MaterialType.transparency,
child: ListTile(
leading: Icon(icon, color: fs.parchment),
title: Text(label, style: TextStyle(color: fs.parchment)),
onTap: onTap,
),
);
}
}
+61 -13
View File
@@ -58,13 +58,13 @@ packages:
source: hosted
version: "0.1.4"
audio_session:
dependency: transitive
dependency: "direct main"
description:
name: audio_session
sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac"
sha256: "7217b229db57cc4dc577a8abb56b7429a5a212b978517a5be578704bfe5e568b"
url: "https://pub.dev"
source: hosted
version: "0.1.25"
version: "0.2.3"
boolean_selector:
dependency: transitive
description:
@@ -249,14 +249,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.dev"
source: hosted
version: "1.0.9"
dart_style:
dependency: transitive
description:
@@ -366,6 +358,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_lucide:
dependency: "direct main"
description:
name: flutter_lucide
sha256: d2866c9ba75b2300e73a888489f0d7ef1d225e1c352d7e7f5c7fdff7d04e294c
url: "https://pub.dev"
source: hosted
version: "1.11.0"
flutter_riverpod:
dependency: "direct main"
description:
@@ -378,10 +378,10 @@ packages:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "8b302d17096ba88f911b7eb317c71d5e691da60a259549f42b38c658d1776d87"
sha256: "6848263f9744072d0977347c383fb8b57d9780319a6bf5238b5a2866a029de62"
url: "https://pub.dev"
source: hosted
version: "10.1.0"
version: "10.2.0"
flutter_secure_storage_darwin:
dependency: transitive
description:
@@ -800,6 +800,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.dev"
source: hosted
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.dev"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.dev"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser:
dependency: transitive
description:
+3 -4
View File
@@ -1,7 +1,7 @@
name: minstrel
description: Minstrel mobile client
publish_to: 'none'
version: 2026.5.19+13
version: 2026.5.21+14
environment:
sdk: '>=3.5.0 <4.0.0'
@@ -19,7 +19,7 @@ dependencies:
# interruption/becoming-noisy itself (just_audio does not). Same author
# as just_audio/audio_service so versions track together.
audio_session: ^0.2.3
flutter_secure_storage: ^10.1.0
flutter_secure_storage: ^10.2.0
go_router: ^17.2.3
flutter_svg: ^2.0.16
# Lucide icon set (design system mandates Lucide, not Material).
@@ -33,7 +33,6 @@ dependencies:
# until either lib bumps win32 to 6.x.
package_info_plus: ^8.3.1
pub_semver: ^2.1.4
cupertino_icons: ^1.0.8
path_provider: ^2.1.5
drift: ^2.18.0
drift_flutter: ^0.2.0
@@ -59,7 +58,7 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^6.0.0
mocktail: ^1.0.4
mocktail: ^1.0.5
drift_dev: ^2.18.0
build_runner: ^2.4.13
+14 -15
View File
@@ -1,6 +1,3 @@
@Tags(['drift'])
library;
import 'dart:io';
import 'package:dio/dio.dart';
@@ -11,13 +8,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:minstrel/cache/audio_cache_manager.dart';
import 'package:minstrel/cache/db.dart';
// See sync_controller_test.dart for the same skip rationale.
const _skipDrift = 'libsqlite3 missing on flutter-ci runner; on-device covers';
AppDb _testDb() => AppDb(NativeDatabase.memory());
void main() {
test('isCached returns false when no row exists', skip: _skipDrift, () async {
test('isCached returns false when no row exists', () async {
final db = _testDb();
addTearDown(db.close);
final mgr = AudioCacheManager(
@@ -29,7 +23,7 @@ void main() {
expect(await mgr.pathFor('nonexistent'), null);
});
test('usageBytes sums sizeBytes across rows', skip: _skipDrift, () async {
test('bucketUsage sums drift sizeBytes across rows', () async {
final db = _testDb();
addTearDown(db.close);
final tmp = Directory.systemTemp.createTempSync();
@@ -38,25 +32,30 @@ void main() {
dioFactory: () async => Dio(),
cacheDirFactory: () async => tmp,
);
// Each row needs a distinct `path` because path is not the primary
// key, but mapping by trackId only works if rows are distinct.
await db.batch((b) {
b.insertAll(db.audioCacheIndex, [
AudioCacheIndexCompanion.insert(
trackId: 'a',
path: 'p',
path: 'p_a',
sizeBytes: 100,
source: CacheSource.manual),
AudioCacheIndexCompanion.insert(
trackId: 'b',
path: 'p',
path: 'p_b',
sizeBytes: 250,
source: CacheSource.incidental),
]);
});
expect(await mgr.usageBytes(), 350);
// usageBytes() is a directory walk (authoritative on-disk total,
// catches orphan partials); for a drift-sizeBytes sum, bucketUsage
// is the correct API. With empty liked set, both rows go to rolling.
final usage = await mgr.bucketUsage(const <String>{});
expect(usage.liked + usage.rolling, 350);
});
test('rolling cap evicts non-liked LRU; liked protected',
skip: _skipDrift, () async {
test('rolling cap evicts non-liked LRU; liked protected', () async {
final db = _testDb();
addTearDown(db.close);
final tmp = Directory.systemTemp.createTempSync();
@@ -108,7 +107,7 @@ void main() {
expect(await mgr.isCached('lik'), true); // liked, protected
});
test('clearAll removes everything including manual', skip: _skipDrift, () async {
test('clearAll removes everything including manual', () async {
final db = _testDb();
addTearDown(db.close);
final tmp = Directory.systemTemp.createTempSync();
@@ -133,7 +132,7 @@ void main() {
expect(await mgr.isCached('man'), false);
});
test('unpin removes index row + deletes file', skip: _skipDrift, () async {
test('unpin removes index row + deletes file', () async {
final db = _testDb();
addTearDown(db.close);
final tmp = Directory.systemTemp.createTempSync();
+15 -16
View File
@@ -1,5 +1,4 @@
@Tags(['drift'])
library;
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:drift/native.dart' show NativeDatabase;
@@ -11,24 +10,24 @@ import 'package:minstrel/cache/db.dart';
import 'package:minstrel/cache/sync_controller.dart';
import 'package:minstrel/library/library_providers.dart' show dioProvider;
// SKIP NOTE (#357 plan B follow-up): drift's NativeDatabase needs the
// system libsqlite3.so. The flutter-ci runner image doesn't ship it, so
// every test in this file errors with "Failed to load dynamic library
// 'libsqlite3.so'". Unblock by either:
// (a) adding libsqlite3-dev to the CI-Runner/CI-flutter image, or
// (b) using sqlite3/wasm + the wasm executor for VM tests.
// Until then, real coverage lives in on-device verification.
const _skipDrift = 'libsqlite3 missing on flutter-ci runner; on-device covers';
/// Builds a Dio whose adapter resolves every request to the supplied
/// status code + body. Avoids touching the network in tests.
///
/// Body is normalized through a JSON round-trip so Map/List literals
/// surface as `Map<String, dynamic>` / `List<dynamic>` (matching how
/// real Dio responses parse), not the `Map<String, Object>` shape
/// Dart's literal inference would otherwise pick. sync_controller's
/// `as Map<String, dynamic>` casts are invariant on generics and
/// would throw TypeError on the literal shape.
Dio _stubDio({required int status, dynamic body}) {
final dio = Dio();
final normalizedBody =
(body is Map || body is List) ? jsonDecode(jsonEncode(body)) : body;
dio.interceptors.add(InterceptorsWrapper(onRequest: (req, h) {
h.resolve(Response<dynamic>(
requestOptions: req,
statusCode: status,
data: body,
data: normalizedBody,
));
}));
return dio;
@@ -42,7 +41,7 @@ ProviderContainer _container({required AppDb db, required Dio dio}) {
}
void main() {
test('204 advances lastSyncAt without changing cursor', skip: _skipDrift, () async {
test('204 advances lastSyncAt without changing cursor', () async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
@@ -56,7 +55,7 @@ void main() {
expect(meta?.lastSyncAt, isNotNull);
});
test('200 with artist upsert writes drift row + advances cursor', skip: _skipDrift, () async {
test('200 with artist upsert writes drift row + advances cursor', () async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
@@ -85,7 +84,7 @@ void main() {
expect(meta?.cursor, 7);
});
test('200 with track delete removes the row', skip: _skipDrift, () async {
test('200 with track delete removes the row', () async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
@@ -115,7 +114,7 @@ void main() {
expect(track, isNull);
});
test('like_track upsert + delete round-trip', skip: _skipDrift, () async {
test('like_track upsert + delete round-trip', () async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
@@ -12,8 +12,7 @@ import 'package:minstrel/playlists/playlists_provider.dart';
import 'package:minstrel/theme/theme_data.dart';
// All tests pump an empty HomeIndex unless they care about populated
// section IDs — per-tile hydration is intentionally not exercised here
// (that requires drift, and the @Tags(['drift']) tier covers it).
// section IDs — per-tile hydration is intentionally not exercised here.
// What this suite verifies is the screen's section-level shape:
// placeholders / empty-state copy / section presence given the index.
const _emptyIndex = HomeIndex.empty;
@@ -1,24 +1,16 @@
@Tags(['drift'])
library;
import 'package:drift/native.dart' show NativeDatabase;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:minstrel/cache/audio_cache_manager.dart' show appDbProvider;
import 'package:minstrel/cache/db.dart';
import 'package:minstrel/library/widgets/album_card.dart';
import 'package:minstrel/library/widgets/track_row.dart';
import 'package:minstrel/models/album.dart';
import 'package:minstrel/models/track.dart';
import 'package:minstrel/theme/theme_data.dart';
// Same drift-cohort skip as the rest. TrackRow now contains
// CachedIndicator (ConsumerWidget), which reads
// audioCacheManagerProvider → constructs AppDb → drift_flutter calls
// driftDatabase() → libsqlite3.so isn't on the runner. Open a real
// async chain that leaves a pending timer at test teardown.
// See Fable #399.
const _skipDrift = true;
void main() {
testWidgets('AlbumCard renders title and artist', (tester) async {
await tester.pumpWidget(MaterialApp(
@@ -39,10 +31,21 @@ void main() {
expect(find.text('Boards of Canada'), findsOneWidget);
});
testWidgets('TrackRow shows mm:ss duration', skip: _skipDrift, (tester) async {
// TrackRow now contains CachedIndicator (ConsumerWidget) so a
// ProviderScope is required at the root.
testWidgets('TrackRow shows mm:ss duration', (tester) async {
// TrackRow contains CachedIndicator (ConsumerWidget) which reaches
// audioCacheManagerProvider → appDbProvider. Override appDbProvider
// with an explicit NativeDatabase.memory() executor — drift_flutter's
// default `driftDatabase()` schedules a deferred-init Timer that
// outlives the test widget tree and trips
// _verifyInvariants("A Timer is still pending after dispose").
await tester.pumpWidget(ProviderScope(
overrides: [
appDbProvider.overrideWith((ref) {
final db = AppDb(NativeDatabase.memory());
ref.onDispose(db.close);
return db;
}),
],
child: MaterialApp(
theme: buildThemeData(),
home: Scaffold(
@@ -1,85 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:minstrel/api/endpoints/likes.dart';
import 'package:minstrel/likes/like_button.dart';
import 'package:minstrel/likes/likes_provider.dart';
import 'package:minstrel/models/album.dart';
import 'package:minstrel/models/artist.dart';
import 'package:minstrel/models/page.dart';
import 'package:minstrel/models/track.dart';
import 'package:minstrel/theme/theme_data.dart';
class _ThrowingLikesApi implements LikesApi {
bool throwOnNext = false;
@override
Future<void> like(LikeKind kind, String id) async {
if (throwOnNext) throw StateError('boom');
}
@override
Future<void> unlike(LikeKind kind, String id) async {
if (throwOnNext) throw StateError('boom');
}
@override
Future<({Set<String> artists, Set<String> albums, Set<String> tracks})>
ids() async =>
(artists: <String>{}, albums: <String>{}, tracks: <String>{});
// The list-* methods aren't exercised by this test; return empty pages.
@override
Future<Paged<TrackRef>> listTracks({int limit = 50, int offset = 0}) async =>
const Paged<TrackRef>(items: [], total: 0, limit: 50, offset: 0);
@override
Future<Paged<AlbumRef>> listAlbums({int limit = 50, int offset = 0}) async =>
const Paged<AlbumRef>(items: [], total: 0, limit: 50, offset: 0);
@override
Future<Paged<ArtistRef>> listArtists({int limit = 50, int offset = 0}) async =>
const Paged<ArtistRef>(items: [], total: 0, limit: 50, offset: 0);
}
// libsqlite3 missing on flutter-ci runner; the rollback assertion now
// depends on drift writes via LikesController. Re-enable when the runner
// image carries libsqlite3-dev (Fable #399).
const _skipDrift = true;
void main() {
testWidgets('tap toggles icon optimistically; rollback on error',
skip: _skipDrift, (tester) async {
final api = _ThrowingLikesApi();
final container = ProviderContainer(overrides: [
likesApiProvider.overrideWith((ref) async => api),
]);
addTearDown(container.dispose);
await tester.pumpWidget(UncontrolledProviderScope(
container: container,
child: MaterialApp(
theme: buildThemeData(),
home: const Scaffold(
body: LikeButton(kind: LikeKind.album, id: 'al-1'),
),
),
));
await tester.pumpAndSettle();
// First tap = like; succeeds → icon flips to favorite.
await tester.tap(find.byType(IconButton));
await tester.pump();
expect(find.byIcon(Icons.favorite), findsOneWidget);
// Force the next mutation to fail; rollback restores prior state.
api.throwOnNext = true;
final controller = container.read(likesControllerProvider);
try {
await controller.toggle(LikeKind.album, 'al-1');
} catch (_) {/* expected */}
await tester.pump();
expect(find.byIcon(Icons.favorite), findsOneWidget); // rolled back
});
}
@@ -1,5 +1,4 @@
@Tags(['drift'])
library;
import 'dart:async';
import 'package:drift/native.dart' show NativeDatabase;
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -7,17 +6,11 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:minstrel/api/endpoints/quarantine.dart';
import 'package:minstrel/cache/audio_cache_manager.dart' show appDbProvider;
import 'package:minstrel/cache/connectivity_provider.dart';
import 'package:minstrel/cache/db.dart';
import 'package:minstrel/models/track.dart';
import 'package:minstrel/quarantine/quarantine_provider.dart';
// SKIP NOTE: drift's NativeDatabase needs the system libsqlite3.so.
// The flutter-ci runner image doesn't ship it. Same skip as
// sync_controller_test.dart — unblock by adding libsqlite3-dev to the
// CI image, or switch to sqlite3/wasm. On-device runs cover real
// behaviour today.
const _skipDrift = 'libsqlite3 missing on flutter-ci runner; on-device covers';
class _StubQuarantineApi implements QuarantineApi {
_StubQuarantineApi({this.shouldThrow = false});
bool shouldThrow;
@@ -53,6 +46,19 @@ ProviderContainer _container({required AppDb db, required QuarantineApi api}) {
return ProviderContainer(overrides: [
appDbProvider.overrideWithValue(db),
quarantineApiProvider.overrideWith((ref) async => api),
// _refreshFromServer() in build() reads connectivityProvider; in tests
// without this override it's a StreamProvider that never emits, so
// tearDown trips Riverpod's "disposed during loading" — visible on
// the throwing-API variant where the catch path's await mutationQueue
// .enqueue advances _refreshFromServer's chain enough to surface it.
// Use a never-closing async* generator instead of Stream.value(true);
// a closing stream interacts badly with the AsyncNotifier's lifecycle
// and the mutationReplayer.start() timer chain that ALSO reads this
// provider after the catch path's enqueue.
connectivityProvider.overrideWith((ref) async* {
yield true;
await Completer<void>().future; // hold open until tearDown
}),
]);
}
@@ -72,7 +78,7 @@ Future<void> _seed(AppDb db) async {
}
void main() {
test('flag inserts a drift row and calls the server', skip: _skipDrift,
test('flag inserts a drift row and calls the server',
() async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
@@ -92,8 +98,7 @@ void main() {
expect(rows.first.reason, 'bad_rip');
});
test('flag keeps drift optimistic + queues mutation on server failure',
skip: _skipDrift, () async {
test('flag keeps drift optimistic + queues mutation on server failure', () async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
final api = _StubQuarantineApi(shouldThrow: true);
@@ -113,9 +118,9 @@ void main() {
expect(mutations, hasLength(1),
reason: 'failed flag should have been enqueued for replay');
expect(mutations.first.kind, 'quarantine.flag');
});
}, skip: 'Pending Fable #476 — StreamProvider<bool> lifecycle in async catch path; see task body for full diagnostic');
test('unflag deletes the drift row and calls the server', skip: _skipDrift,
test('unflag deletes the drift row and calls the server',
() async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
@@ -132,7 +137,7 @@ void main() {
expect(rows, isEmpty);
});
test('isHidden reflects drift state', skip: _skipDrift, () async {
test('isHidden reflects drift state', () async {
final db = AppDb(NativeDatabase.memory());
addTearDown(db.close);
await _seed(db);
@@ -1,6 +1,3 @@
@Tags(['drift'])
library;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@@ -13,22 +10,12 @@ import 'package:minstrel/theme/theme_data.dart';
class _MockStorage extends Mock implements FlutterSecureStorage {}
// StorageSection.initState calls audioCacheManagerProvider.usageBytes()
// which opens the AppDb via NativeDatabase. CI's flutter-ci runner
// lacks libsqlite3.so so this silently emits a warning today and would
// break under stricter analyze. Skipped with the drift cohort.
//
// testWidgets only accepts a bool for skip (unlike test() which takes
// a String reason). Reason: 'libsqlite3 missing on flutter-ci runner;
// on-device covers'. See Fable #399.
const _skipDrift = true;
void main() {
setUpAll(() {
registerFallbackValue('');
});
testWidgets('renders Storage heading + all controls', skip: _skipDrift, (t) async {
testWidgets('renders Storage heading + all controls', (t) async {
final storage = _MockStorage();
when(() => storage.read(key: any(named: 'key')))
.thenAnswer((_) async => null);
@@ -57,7 +44,7 @@ void main() {
expect(find.byKey(const Key('prefetch_selector')), findsOneWidget);
});
testWidgets('Clear cache button shows confirm dialog', skip: _skipDrift, (t) async {
testWidgets('Clear cache button shows confirm dialog', (t) async {
final storage = _MockStorage();
when(() => storage.read(key: any(named: 'key')))
.thenAnswer((_) async => null);
+9 -12
View File
@@ -1,32 +1,29 @@
module git.fabledsword.com/bvandeusen/minstrel
go 1.23.0
go 1.25.0
require (
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
github.com/go-chi/chi/v5 v5.2.5
github.com/go-co-op/gocron/v2 v2.21.2
github.com/golang-migrate/migrate/v4 v4.18.2
github.com/golang-migrate/migrate/v4 v4.19.1
github.com/google/uuid v1.6.0
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa
github.com/jackc/pgx/v5 v5.7.4
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6
github.com/jackc/pgx/v5 v5.9.2
github.com/stretchr/testify v1.11.1
golang.org/x/crypto v0.35.0
golang.org/x/crypto v0.51.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
go.uber.org/atomic v1.7.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/text v0.37.0 // indirect
)
+38 -38
View File
@@ -2,17 +2,21 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4mA/WVIYtpzVm63vLVAPzJXigg=
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E=
github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8=
github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM=
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4=
github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
@@ -23,29 +27,24 @@ github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
github.com/go-co-op/gocron/v2 v2.21.2 h1:bD8/YwkojYHgXFr3iEulL148KBdTbKVxUZzFKpXcdbY=
github.com/go-co-op/gocron/v2 v2.21.2/go.mod h1:5lEiCKk1oVJV39Zg7/YG10OnaVrDAV5GGR6O0663k6U=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8=
github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk=
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6 h1:D/V0gu4zQ3cL2WKeVNVM4r2gLxGGf6McLwgXzRTo2RQ=
github.com/jackc/pgerrcode v0.0.0-20250907135507-afb5586c32a6/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
@@ -68,8 +67,9 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -79,26 +79,26 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+2 -9
View File
@@ -100,11 +100,7 @@ func RunScan(
var lastStats Stats
libPub := newStagePublisher(ctx, 500, time.Second,
func() []byte {
blob, _ := json.Marshal(LibraryStageTallies{
Scanned: lastStats.Scanned, Added: lastStats.Added,
Updated: lastStats.Updated, Skipped: lastStats.Skipped,
Errored: lastStats.Errored,
})
blob, _ := json.Marshal(LibraryStageTallies(lastStats))
return blob
},
func(blob []byte) error {
@@ -131,10 +127,7 @@ func RunScan(
var lastRes BackfillMBIDsResult
bfPub := newStagePublisher(ctx, 500, time.Second,
func() []byte {
blob, _ := json.Marshal(MBIDBackfillStageTallies{
Processed: lastRes.Processed, Healed: lastRes.Healed,
Skipped: lastRes.Skipped, Duplicates: lastRes.Duplicates,
})
blob, _ := json.Marshal(MBIDBackfillStageTallies(lastRes))
return blob
},
func(blob []byte) error {
+2 -9
View File
@@ -145,10 +145,7 @@ func loadRediscoverAlbums(ctx context.Context, q *dbq.Queries, userID pgtype.UUI
if _, dup := seen[r.Album.ID]; dup {
continue
}
primary = append(primary, dbq.ListRediscoverAlbumsForUserRow{
Album: r.Album,
ArtistName: r.ArtistName,
})
primary = append(primary, dbq.ListRediscoverAlbumsForUserRow(r))
if len(primary) >= HomeRediscoverLimit {
break
}
@@ -181,11 +178,7 @@ func loadRediscoverArtists(ctx context.Context, q *dbq.Queries, userID pgtype.UU
if _, dup := seen[r.Artist.ID]; dup {
continue
}
primary = append(primary, dbq.ListRediscoverArtistsForUserRow{
Artist: r.Artist,
CoverAlbumID: r.CoverAlbumID,
AlbumCount: r.AlbumCount,
})
primary = append(primary, dbq.ListRediscoverArtistsForUserRow(r))
if len(primary) >= HomeRediscoverLimit {
break
}
+219 -154
View File
@@ -6,7 +6,7 @@
"": {
"name": "minstrel-tools",
"devDependencies": {
"sharp": "^0.33.5"
"sharp": "^0.34.5"
}
},
"node_modules/@emnapi/runtime": {
@@ -20,10 +20,20 @@
"tslib": "^2.4.0"
}
},
"node_modules/@img/colour": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
"integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
"integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
"cpu": [
"arm64"
],
@@ -40,13 +50,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.4"
"@img/sharp-libvips-darwin-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-darwin-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
"integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
"cpu": [
"x64"
],
@@ -63,13 +73,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.4"
"@img/sharp-libvips-darwin-x64": "1.2.4"
}
},
"node_modules/@img/sharp-libvips-darwin-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
"integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
"cpu": [
"arm64"
],
@@ -84,9 +94,9 @@
}
},
"node_modules/@img/sharp-libvips-darwin-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
"integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
"cpu": [
"x64"
],
@@ -101,9 +111,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
"integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
"cpu": [
"arm"
],
@@ -121,9 +131,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
"integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
"cpu": [
"arm64"
],
@@ -140,10 +150,50 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-ppc64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-riscv64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
"linux"
],
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-libvips-linux-s390x": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
"integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
"cpu": [
"s390x"
],
@@ -161,9 +211,9 @@
}
},
"node_modules/@img/sharp-libvips-linux-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
"integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
"cpu": [
"x64"
],
@@ -181,9 +231,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-arm64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
"integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
"cpu": [
"arm64"
],
@@ -201,9 +251,9 @@
}
},
"node_modules/@img/sharp-libvips-linuxmusl-x64": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
"integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
"cpu": [
"x64"
],
@@ -221,9 +271,9 @@
}
},
"node_modules/@img/sharp-linux-arm": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
"integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
"cpu": [
"arm"
],
@@ -243,13 +293,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.5"
"@img/sharp-libvips-linux-arm": "1.2.4"
}
},
"node_modules/@img/sharp-linux-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
"integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
"cpu": [
"arm64"
],
@@ -269,13 +319,65 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.4"
"@img/sharp-libvips-linux-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-ppc64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-ppc64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-riscv64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-riscv64": "1.2.4"
}
},
"node_modules/@img/sharp-linux-s390x": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
"integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
"cpu": [
"s390x"
],
@@ -295,13 +397,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.4"
"@img/sharp-libvips-linux-s390x": "1.2.4"
}
},
"node_modules/@img/sharp-linux-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
"integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
"cpu": [
"x64"
],
@@ -321,13 +423,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.4"
"@img/sharp-libvips-linux-x64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
"integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
"cpu": [
"arm64"
],
@@ -347,13 +449,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
}
},
"node_modules/@img/sharp-linuxmusl-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
"integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
"cpu": [
"x64"
],
@@ -373,13 +475,13 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.4"
"@img/sharp-libvips-linuxmusl-x64": "1.2.4"
}
},
"node_modules/@img/sharp-wasm32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
"integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
"cpu": [
"wasm32"
],
@@ -387,7 +489,7 @@
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
"@emnapi/runtime": "^1.2.0"
"@emnapi/runtime": "^1.7.0"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -396,10 +498,30 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-arm64": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
}
},
"node_modules/@img/sharp-win32-ia32": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
"integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
"cpu": [
"ia32"
],
@@ -417,9 +539,9 @@
}
},
"node_modules/@img/sharp-win32-x64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
"integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
"cpu": [
"x64"
],
@@ -436,51 +558,6 @@
"url": "https://opencollective.com/libvips"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -491,13 +568,6 @@
"node": ">=8"
}
},
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"dev": true,
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@@ -512,16 +582,16 @@
}
},
"node_modules/sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.3",
"semver": "^7.6.3"
"@img/colour": "^1.0.0",
"detect-libc": "^2.1.2",
"semver": "^7.7.3"
},
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
@@ -530,35 +600,30 @@
"url": "https://opencollective.com/libvips"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.5",
"@img/sharp-darwin-x64": "0.33.5",
"@img/sharp-libvips-darwin-arm64": "1.0.4",
"@img/sharp-libvips-darwin-x64": "1.0.4",
"@img/sharp-libvips-linux-arm": "1.0.5",
"@img/sharp-libvips-linux-arm64": "1.0.4",
"@img/sharp-libvips-linux-s390x": "1.0.4",
"@img/sharp-libvips-linux-x64": "1.0.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.4",
"@img/sharp-linux-arm": "0.33.5",
"@img/sharp-linux-arm64": "0.33.5",
"@img/sharp-linux-s390x": "0.33.5",
"@img/sharp-linux-x64": "0.33.5",
"@img/sharp-linuxmusl-arm64": "0.33.5",
"@img/sharp-linuxmusl-x64": "0.33.5",
"@img/sharp-wasm32": "0.33.5",
"@img/sharp-win32-ia32": "0.33.5",
"@img/sharp-win32-x64": "0.33.5"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
"@img/sharp-darwin-arm64": "0.34.5",
"@img/sharp-darwin-x64": "0.34.5",
"@img/sharp-libvips-darwin-arm64": "1.2.4",
"@img/sharp-libvips-darwin-x64": "1.2.4",
"@img/sharp-libvips-linux-arm": "1.2.4",
"@img/sharp-libvips-linux-arm64": "1.2.4",
"@img/sharp-libvips-linux-ppc64": "1.2.4",
"@img/sharp-libvips-linux-riscv64": "1.2.4",
"@img/sharp-libvips-linux-s390x": "1.2.4",
"@img/sharp-libvips-linux-x64": "1.2.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
"@img/sharp-libvips-linuxmusl-x64": "1.2.4",
"@img/sharp-linux-arm": "0.34.5",
"@img/sharp-linux-arm64": "0.34.5",
"@img/sharp-linux-ppc64": "0.34.5",
"@img/sharp-linux-riscv64": "0.34.5",
"@img/sharp-linux-s390x": "0.34.5",
"@img/sharp-linux-x64": "0.34.5",
"@img/sharp-linuxmusl-arm64": "0.34.5",
"@img/sharp-linuxmusl-x64": "0.34.5",
"@img/sharp-wasm32": "0.34.5",
"@img/sharp-win32-arm64": "0.34.5",
"@img/sharp-win32-ia32": "0.34.5",
"@img/sharp-win32-x64": "0.34.5"
}
},
"node_modules/tslib": {
+1 -1
View File
@@ -6,6 +6,6 @@
"gen-og-placeholder": "node gen-og-placeholder.mjs"
},
"devDependencies": {
"sharp": "^0.33.5"
"sharp": "^0.34.5"
}
}
+22 -30
View File
@@ -13,19 +13,18 @@
"lucide-svelte": "^1.0.1"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.8.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.3.1",
"@types/node": "^25.6.0",
"autoprefixer": "^10.4.20",
"@types/node": "^25.9.1",
"autoprefixer": "^10.5.0",
"jsdom": "^25.0.1",
"postcss": "^8.4.49",
"postcss": "^8.5.15",
"svelte": "^5.1.9",
"svelte-check": "^4.0.5",
"svelte-check": "^4.4.8",
"tailwindcss": "^3.4.14",
"tslib": "^2.8.0",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vitest": "^2.1.4"
@@ -1346,13 +1345,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.6.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
"version": "25.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.19.0"
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/@types/trusted-types": {
@@ -2807,9 +2806,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"dev": true,
"funding": [
{
@@ -2947,9 +2946,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"dev": true,
"funding": [
{
@@ -2967,7 +2966,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -3472,9 +3471,9 @@
}
},
"node_modules/svelte-check": {
"version": "4.4.6",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.6.tgz",
"integrity": "sha512-kP1zG81EWaFe9ZyTv4ZXv44Csi6Pkdpb7S3oj6m+K2ec/IcDg/a8LsFsnVLqm2nxtkSwsd5xPj/qFkTBgXHXjg==",
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.8.tgz",
"integrity": "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3764,13 +3763,6 @@
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
"license": "0BSD"
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -3786,9 +3778,9 @@
}
},
"node_modules/undici-types": {
"version": "7.19.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT"
},
+5 -6
View File
@@ -12,19 +12,18 @@
"tokens": "node scripts/tokens-to-css.js"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/adapter-static": "^3.0.10",
"@sveltejs/kit": "^2.8.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.3.1",
"@types/node": "^25.6.0",
"autoprefixer": "^10.4.20",
"@types/node": "^25.9.1",
"autoprefixer": "^10.5.0",
"jsdom": "^25.0.1",
"postcss": "^8.4.49",
"postcss": "^8.5.15",
"svelte": "^5.1.9",
"svelte-check": "^4.0.5",
"svelte-check": "^4.4.8",
"tailwindcss": "^3.4.14",
"tslib": "^2.8.0",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vitest": "^2.1.4"