From 95ca970442509c1c134b09cd230046d485da58f3 Mon Sep 17 00:00:00 2001 From: hmjn Date: Fri, 21 Aug 2026 00:07:30 +0900 Subject: [PATCH 1/3] feat(search): persist search results in browser history Store result-defining search snapshots in browser history entries and resolve them through the server for back/forward navigation. Add the snapshot API, database migration, Web/Tauri route wiring, E2E coverage, and refreshed indexion architecture docs. --- .../drizzle/0026_nebulous_metal_master.sql | 10 + apps/server/drizzle/meta/0026_snapshot.json | 3391 +++++++++++ apps/server/drizzle/meta/_journal.json | 7 + apps/server/public/openapi.json | 258 + .../src/infrastructure/api/app-router.ts | 2 + .../api/clients/search-history-client.ts | 6 + .../src/infrastructure/api/openapi-tags.ts | 5 + .../api/routers/search-snapshots-router.ts | 13 + .../search-snapshot-repository.ts | 6 + .../services/search-snapshot-service.ts | 21 + apps/server/src/routes/search.tsx | 23 +- .../components/legacy-source-media-page.tsx | 5 + .../components/source-media-page.tsx | 4 +- .../components/v2-source-media-page.tsx | 5 + .../routes/sources/$mediaSourceId/index.tsx | 2 + .../v2/components/v2-search-content.tsx | 21 +- apps/server/src/routes/v2/search.tsx | 2 + .../v2/sources/$mediaSourceId/index.tsx | 2 + .../src/tests/e2e/search-history.spec.ts | 77 + .../services/search-snapshot-service.test.ts | 72 + .../api/clients/search-history-client.ts | 6 + apps/tauri/src/routes/search.tsx | 19 +- .../components/source-media-page.tsx | 4 + .../routes/sources/$mediaSourceId/index.tsx | 2 + ...exion-dependency-graph-01-apps-and-core.md | 950 +-- ...dexion-dependency-graph-02-server-tests.md | 197 +- ...y-graph-03-server-infrastructure-routes.md | 1621 +++--- ...-graph-04-server-application-components.md | 115 +- .../indexion-dependency-graph-05-ui.md | 5 + .../indexion-dependency-graph-full.md | 5186 +++++++++-------- .../architecture/indexion-dependency-graph.md | 14 +- docs/architecture/package-dependency-graph.md | 24 +- docs/architecture/search-history.md | 66 + packages/application/src/index.ts | 2 + packages/application/src/ports/index.ts | 1 + .../src/ports/search-snapshot-service.ts | 9 + packages/application/src/services/index.ts | 1 + .../src/services/search-snapshot-service.ts | 55 + packages/core/src/domain/contract/index.ts | 4 + .../contract/search-snapshots-client.ts | 6 + .../contract/search-snapshots.contract.ts | 14 + .../search-snapshot-repository.ts | 13 + packages/core/src/domain/search/history.ts | 54 + packages/core/src/domain/sources/store.ts | 28 +- packages/db/src/index.ts | 1 + .../search-snapshot-repository.ts | 64 + packages/db/src/schema.ts | 24 + .../hooks/use-current-search-persistence.ts | 42 +- .../hooks/use-search-history-persistence.ts | 296 + packages/ui/src/hooks/use-search-page.ts | 7 +- .../ui/src/hooks/use-source-media-page.ts | 21 +- packages/ui/src/search-history-client.ts | 19 + packages/ui/src/search-history-route.ts | 5 + packages/ui/src/source-media-page.tsx | 19 +- 54 files changed, 8843 insertions(+), 3983 deletions(-) create mode 100644 apps/server/drizzle/0026_nebulous_metal_master.sql create mode 100644 apps/server/drizzle/meta/0026_snapshot.json create mode 100644 apps/server/src/infrastructure/api/clients/search-history-client.ts create mode 100644 apps/server/src/infrastructure/api/routers/search-snapshots-router.ts create mode 100644 apps/server/src/infrastructure/repositories/search-snapshot-repository.ts create mode 100644 apps/server/src/infrastructure/services/search-snapshot-service.ts create mode 100644 apps/server/src/tests/e2e/search-history.spec.ts create mode 100644 apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts create mode 100644 apps/tauri/src/infrastructure/api/clients/search-history-client.ts create mode 100644 docs/architecture/search-history.md create mode 100644 packages/application/src/ports/search-snapshot-service.ts create mode 100644 packages/application/src/services/search-snapshot-service.ts create mode 100644 packages/core/src/domain/contract/search-snapshots-client.ts create mode 100644 packages/core/src/domain/contract/search-snapshots.contract.ts create mode 100644 packages/core/src/domain/repositories/search-snapshot-repository.ts create mode 100644 packages/core/src/domain/search/history.ts create mode 100644 packages/db/src/repositories/search-snapshot-repository.ts create mode 100644 packages/ui/src/hooks/use-search-history-persistence.ts create mode 100644 packages/ui/src/search-history-client.ts create mode 100644 packages/ui/src/search-history-route.ts diff --git a/apps/server/drizzle/0026_nebulous_metal_master.sql b/apps/server/drizzle/0026_nebulous_metal_master.sql new file mode 100644 index 000000000..6a1dafb74 --- /dev/null +++ b/apps/server/drizzle/0026_nebulous_metal_master.sql @@ -0,0 +1,10 @@ +CREATE TABLE "search_snapshots" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "version" integer DEFAULT 1 NOT NULL, + "fingerprint" text NOT NULL, + "state" jsonb NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "search_snapshots_fingerprint_unique" UNIQUE("fingerprint") +); +--> statement-breakpoint +CREATE INDEX "idx_search_snapshots_created_at" ON "search_snapshots" USING btree ("created_at"); \ No newline at end of file diff --git a/apps/server/drizzle/meta/0026_snapshot.json b/apps/server/drizzle/meta/0026_snapshot.json new file mode 100644 index 000000000..ad1355b15 --- /dev/null +++ b/apps/server/drizzle/meta/0026_snapshot.json @@ -0,0 +1,3391 @@ +{ + "id": "ed86b785-ed10-4de8-94e6-a389bee87ea7", + "prevId": "65fa6c9c-2e70-45bb-ab6d-04644838f7d0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.author_accounts": { + "name": "author_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "author_platform", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_url": { + "name": "profile_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_author_accounts_author_id": { + "name": "idx_author_accounts_author_id", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_author_accounts_platform_account_unique": { + "name": "idx_author_accounts_platform_account_unique", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "author_accounts_author_id_authors_id_fk": { + "name": "author_accounts_author_id_authors_id_fk", + "tableFrom": "author_accounts", + "tableTo": "authors", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.authors": { + "name": "authors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_authors_account_id": { + "name": "idx_authors_account_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_authors_name": { + "name": "idx_authors_name", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.categories": { + "name": "categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#808080'" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "categories_parent_id_categories_id_fk": { + "name": "categories_parent_id_categories_id_fk", + "tableFrom": "categories", + "tableTo": "categories", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "categories_name_unique": { + "name": "categories_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ccip_embeddings": { + "name": "ccip_embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "region_id": { + "name": "region_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(768)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding_version": { + "name": "embedding_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "media_modified_at": { + "name": "media_modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "extracted_at": { + "name": "extracted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_ccip_embeddings_region_id": { + "name": "idx_ccip_embeddings_region_id", + "columns": [ + { + "expression": "region_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_ccip_embeddings_embedding_cosine": { + "name": "idx_ccip_embeddings_embedding_cosine", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + } + }, + "foreignKeys": { + "ccip_embeddings_region_id_media_regions_id_fk": { + "name": "ccip_embeddings_region_id_media_regions_id_fk", + "tableFrom": "ccip_embeddings", + "tableTo": "media_regions", + "columnsFrom": [ + "region_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_ccip_embeddings_region_model_version": { + "name": "uq_ccip_embeddings_region_model_version", + "nullsNotDistinct": false, + "columns": [ + "region_id", + "model", + "embedding_version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.character_ips": { + "name": "character_ips", + "schema": "", + "columns": { + "character_id": { + "name": "character_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ip_id": { + "name": "ip_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + } + }, + "indexes": { + "idx_character_ips_ip_id_character_id": { + "name": "idx_character_ips_ip_id_character_id", + "columns": [ + { + "expression": "ip_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "character_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "character_ips_character_id_characters_id_fk": { + "name": "character_ips_character_id_characters_id_fk", + "tableFrom": "character_ips", + "tableTo": "characters", + "columnsFrom": [ + "character_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "character_ips_ip_id_ips_id_fk": { + "name": "character_ips_ip_id_ips_id_fk", + "tableFrom": "character_ips", + "tableTo": "ips", + "columnsFrom": [ + "ip_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "character_ips_character_id_ip_id_pk": { + "name": "character_ips_character_id_ip_id_pk", + "columns": [ + "character_id", + "ip_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.characters": { + "name": "characters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "aliases": { + "name": "aliases", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "characters_name_unique": { + "name": "characters_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ips": { + "name": "ips", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ips_name_unique": { + "name": "ips_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.jobs": { + "name": "jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "job_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "parent_id": { + "name": "parent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cancel_requested_at": { + "name": "cancel_requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "artifact_path": { + "name": "artifact_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "artifact_file_name": { + "name": "artifact_file_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "artifact_content_type": { + "name": "artifact_content_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "artifact_size": { + "name": "artifact_size", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "artifact_expires_at": { + "name": "artifact_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_jobs_pending_import_request": { + "name": "idx_jobs_pending_import_request", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"jobs\".\"status\" = 'pending' AND \"jobs\".\"type\" = 'import_request'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_jobs_pending_created": { + "name": "idx_jobs_pending_created", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"jobs\".\"status\" = 'pending' AND \"jobs\".\"type\" <> 'import_request'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_jobs_pending_type_created": { + "name": "idx_jobs_pending_type_created", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"jobs\".\"status\" = 'pending' AND \"jobs\".\"type\" <> 'import_request'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_jobs_active_thumbnail": { + "name": "uq_jobs_active_thumbnail", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"payload\"->>'mediaId')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "(\"payload\"->>'size')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"jobs\".\"type\" = 'generate_thumbnail'\n\t\t\t\t\tAND \"jobs\".\"status\" IN ('pending', 'in_progress')\n\t\t\t\t\tAND \"jobs\".\"source_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_jobs_cancelable": { + "name": "idx_jobs_cancelable", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"jobs\".\"status\" IN ('pending', 'in_progress')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_jobs_artifact_expiry": { + "name": "idx_jobs_artifact_expiry", + "columns": [ + { + "expression": "artifact_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"jobs\".\"artifact_path\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "jobs_source_id_media_sources_id_fk": { + "name": "jobs_source_id_media_sources_id_fk", + "tableFrom": "jobs", + "tableTo": "media_sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "jobs_parent_id_jobs_id_fk": { + "name": "jobs_parent_id_jobs_id_fk", + "tableFrom": "jobs", + "tableTo": "jobs", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_authors": { + "name": "media_authors", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_media_authors_author_id_media_id": { + "name": "idx_media_authors_author_id_media_id", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_authors_media_id_media_id_fk": { + "name": "media_authors_media_id_media_id_fk", + "tableFrom": "media_authors", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_authors_author_id_authors_id_fk": { + "name": "media_authors_author_id_authors_id_fk", + "tableFrom": "media_authors", + "tableTo": "authors", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_authors_media_id_author_id_pk": { + "name": "media_authors_media_id_author_id_pk", + "columns": [ + "media_id", + "author_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_categories": { + "name": "media_categories", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_media_categories_category_id_media_id": { + "name": "idx_media_categories_category_id_media_id", + "columns": [ + { + "expression": "category_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_categories_media_id_media_id_fk": { + "name": "media_categories_media_id_media_id_fk", + "tableFrom": "media_categories", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_categories_category_id_categories_id_fk": { + "name": "media_categories_category_id_categories_id_fk", + "tableFrom": "media_categories", + "tableTo": "categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_categories_media_id_category_id_pk": { + "name": "media_categories_media_id_category_id_pk", + "columns": [ + "media_id", + "category_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_characters": { + "name": "media_characters", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "character_id": { + "name": "character_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + } + }, + "indexes": { + "idx_media_characters_character_id_media_id": { + "name": "idx_media_characters_character_id_media_id", + "columns": [ + { + "expression": "character_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_characters_media_id_media_id_fk": { + "name": "media_characters_media_id_media_id_fk", + "tableFrom": "media_characters", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_characters_character_id_characters_id_fk": { + "name": "media_characters_character_id_characters_id_fk", + "tableFrom": "media_characters", + "tableTo": "characters", + "columnsFrom": [ + "character_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_characters_media_id_character_id_pk": { + "name": "media_characters_media_id_character_id_pk", + "columns": [ + "media_id", + "character_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_collections": { + "name": "media_collections", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_media_collections_media_id": { + "name": "idx_media_collections_media_id", + "columns": [ + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_collections_collection_id_collections_id_fk": { + "name": "media_collections_collection_id_collections_id_fk", + "tableFrom": "media_collections", + "tableTo": "collections", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_collections_media_id_media_id_fk": { + "name": "media_collections_media_id_media_id_fk", + "tableFrom": "media_collections", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_collections_collection_id_media_id_pk": { + "name": "media_collections_collection_id_media_id_pk", + "columns": [ + "collection_id", + "media_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_details": { + "name": "media_details", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "favorite": { + "name": "favorite", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "view_count": { + "name": "view_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_viewed_at": { + "name": "last_viewed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "'1970-01-01 00:00:00'" + } + }, + "indexes": { + "idx_media_details_rating": { + "name": "idx_media_details_rating", + "columns": [ + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_details_favorite": { + "name": "idx_media_details_favorite", + "columns": [ + { + "expression": "favorite", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_details_view_count": { + "name": "idx_media_details_view_count", + "columns": [ + { + "expression": "view_count", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_details_media_id_media_id_fk": { + "name": "media_details_media_id_media_id_fk", + "tableFrom": "media_details", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_generation_info": { + "name": "media_generation_info", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "negative_prompt": { + "name": "negative_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow": { + "name": "workflow", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "loras": { + "name": "loras", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "vae": { + "name": "vae", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hypernetworks": { + "name": "hypernetworks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "embeddings": { + "name": "embeddings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ai_generated": { + "name": "ai_generated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "model_name": { + "name": "model_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "seed": { + "name": "seed", + "type": "bigint", + "primaryKey": false, + "notNull": false, + "default": -1 + }, + "cfg_scale": { + "name": "cfg_scale", + "type": "real", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "steps": { + "name": "steps", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + } + }, + "indexes": { + "idx_media_generation_info_metadata": { + "name": "idx_media_generation_info_metadata", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_generation_info_ai_generated": { + "name": "idx_media_generation_info_ai_generated", + "columns": [ + { + "expression": "ai_generated", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_generation_info_model_name": { + "name": "idx_media_generation_info_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_generation_info_media_id_media_id_fk": { + "name": "media_generation_info_media_id_media_id_fk", + "tableFrom": "media_generation_info", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_ips": { + "name": "media_ips", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ip_id": { + "name": "ip_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + } + }, + "indexes": { + "idx_media_ips_ip_id_media_id": { + "name": "idx_media_ips_ip_id_media_id", + "columns": [ + { + "expression": "ip_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_ips_media_id_media_id_fk": { + "name": "media_ips_media_id_media_id_fk", + "tableFrom": "media_ips", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_ips_ip_id_ips_id_fk": { + "name": "media_ips_ip_id_ips_id_fk", + "tableFrom": "media_ips", + "tableTo": "ips", + "columnsFrom": [ + "ip_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_ips_media_id_ip_id_pk": { + "name": "media_ips_media_id_ip_id_pk", + "columns": [ + "media_id", + "ip_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_projects": { + "name": "media_projects", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_media_projects_project_id_media_id": { + "name": "idx_media_projects_project_id_media_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_projects_media_id_media_id_fk": { + "name": "media_projects_media_id_media_id_fk", + "tableFrom": "media_projects", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_projects_project_id_projects_id_fk": { + "name": "media_projects_project_id_projects_id_fk", + "tableFrom": "media_projects", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_projects_media_id_project_id_pk": { + "name": "media_projects_media_id_project_id_pk", + "columns": [ + "media_id", + "project_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_regions": { + "name": "media_regions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "media_region_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "x": { + "name": "x", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "y": { + "name": "y", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "source_modified_at": { + "name": "source_modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "detector": { + "name": "detector", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detector_version": { + "name": "detector_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "score": { + "name": "score", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_media_regions_media_id": { + "name": "idx_media_regions_media_id", + "columns": [ + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_media_regions_full_media_id": { + "name": "uq_media_regions_full_media_id", + "columns": [ + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"media_regions\".\"kind\" = 'full'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_regions_media_id_media_id_fk": { + "name": "media_regions_media_id_media_id_fk", + "tableFrom": "media_regions", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "media_regions_bbox_by_kind": { + "name": "media_regions_bbox_by_kind", + "value": "(\n\t\t\t\t(\"media_regions\".\"kind\" = 'full' AND \"media_regions\".\"x\" IS NULL AND \"media_regions\".\"y\" IS NULL AND \"media_regions\".\"width\" IS NULL AND \"media_regions\".\"height\" IS NULL)\n\t\t\t\tOR\n\t\t\t\t(\"media_regions\".\"kind\" <> 'full' AND \"media_regions\".\"x\" IS NOT NULL AND \"media_regions\".\"y\" IS NOT NULL AND \"media_regions\".\"width\" IS NOT NULL AND \"media_regions\".\"height\" IS NOT NULL\n\t\t\t\t\tAND \"media_regions\".\"x\" >= 0 AND \"media_regions\".\"y\" >= 0 AND \"media_regions\".\"width\" > 0 AND \"media_regions\".\"height\" > 0\n\t\t\t\t\tAND \"media_regions\".\"x\" + \"media_regions\".\"width\" <= 1 AND \"media_regions\".\"y\" + \"media_regions\".\"height\" <= 1)\n\t\t\t)" + }, + "media_regions_score_range": { + "name": "media_regions_score_range", + "value": "\"media_regions\".\"score\" IS NULL OR (\"media_regions\".\"score\" >= 0 AND \"media_regions\".\"score\" <= 1)" + } + }, + "isRLSEnabled": false + }, + "public.media_relations": { + "name": "media_relations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "parent_media_id": { + "name": "parent_media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "child_media_id": { + "name": "child_media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "relation_type": { + "name": "relation_type", + "type": "media_relation_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_index": { + "name": "order_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_media_relations_child": { + "name": "idx_media_relations_child", + "columns": [ + { + "expression": "child_media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_relations_type": { + "name": "idx_media_relations_type", + "columns": [ + { + "expression": "relation_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_relations_parent_media_id_media_id_fk": { + "name": "media_relations_parent_media_id_media_id_fk", + "tableFrom": "media_relations", + "tableTo": "media", + "columnsFrom": [ + "parent_media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_relations_child_media_id_media_id_fk": { + "name": "media_relations_child_media_id_media_id_fk", + "tableFrom": "media_relations", + "tableTo": "media", + "columnsFrom": [ + "child_media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "parent_child_type_unique": { + "name": "parent_child_type_unique", + "nullsNotDistinct": false, + "columns": [ + "parent_media_id", + "child_media_id", + "relation_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_sources": { + "name": "media_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "media_source_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "connection_info": { + "name": "connection_info", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "sync_status": { + "name": "sync_status", + "type": "media_source_sync_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "last_sync_started_at": { + "name": "last_sync_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_completed_at": { + "name": "last_sync_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_sync": { + "name": "media_sync", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "sync_status": { + "name": "sync_status", + "type": "media_sync_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'synced'" + }, + "backup_urls": { + "name": "backup_urls", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sync_attempts": { + "name": "sync_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "media_sync_media_id_media_id_fk": { + "name": "media_sync_media_id_media_id_fk", + "tableFrom": "media_sync", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_tags": { + "name": "media_tags", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tag_type": { + "name": "tag_type", + "type": "tag_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'positive'" + }, + "confidence": { + "name": "confidence", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + } + }, + "indexes": { + "idx_media_tags_tag_id_tag_type_media_id": { + "name": "idx_media_tags_tag_id_tag_type_media_id", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_tags_media_id_media_id_fk": { + "name": "media_tags_media_id_media_id_fk", + "tableFrom": "media_tags", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "media_tags_tag_id_tags_id_fk": { + "name": "media_tags_tag_id_tags_id_fk", + "tableFrom": "media_tags", + "tableTo": "tags", + "columnsFrom": [ + "tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "media_tags_media_id_tag_id_tag_type_pk": { + "name": "media_tags_media_id_tag_id_tag_type_pk", + "columns": [ + "media_id", + "tag_id", + "tag_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_technical_info": { + "name": "media_technical_info", + "schema": "", + "columns": { + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "color_profile": { + "name": "color_profile", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "exif_data": { + "name": "exif_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "hash_md5": { + "name": "hash_md5", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "hash_perceptual": { + "name": "hash_perceptual", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "frame_rate": { + "name": "frame_rate", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "bitrate_kbps": { + "name": "bitrate_kbps", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "video_codec": { + "name": "video_codec", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "audio_codec": { + "name": "audio_codec", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_media_technical_info_hash_md5": { + "name": "idx_media_technical_info_hash_md5", + "columns": [ + { + "expression": "hash_md5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_technical_info_media_id_media_id_fk": { + "name": "media_technical_info_media_id_media_id_fk", + "tableFrom": "media_technical_info", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media_urls": { + "name": "media_urls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_media_urls_media_id": { + "name": "idx_media_urls_media_id", + "columns": [ + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_urls_url": { + "name": "idx_media_urls_url", + "columns": [ + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_urls_media_id_url_unique": { + "name": "idx_media_urls_media_id_url_unique", + "columns": [ + { + "expression": "media_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_urls_media_id_media_id_fk": { + "name": "media_urls_media_id_media_id_fk", + "tableFrom": "media_urls", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.media": { + "name": "media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_id": { + "name": "source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "media_type": { + "name": "media_type", + "type": "media_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "modified_at": { + "name": "modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "indexed_at": { + "name": "indexed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "media_organization_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + } + }, + "indexes": { + "idx_media_source_id": { + "name": "idx_media_source_id", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_file_size": { + "name": "idx_media_file_size", + "columns": [ + { + "expression": "file_size", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_file_name": { + "name": "idx_media_file_name", + "columns": [ + { + "expression": "file_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_created_at": { + "name": "idx_media_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_media_description": { + "name": "idx_media_description", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "media_source_id_media_sources_id_fk": { + "name": "media_source_id_media_sources_id_fk", + "tableFrom": "media", + "tableTo": "media_sources", + "columnsFrom": [ + "source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "source_id_file_path_unique": { + "name": "source_id_file_path_unique", + "nullsNotDistinct": false, + "columns": [ + "source_id", + "file_path" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.presets": { + "name": "presets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "sort": { + "name": "sort", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "presets_name_unique": { + "name": "presets_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projects": { + "name": "projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_projects_name": { + "name": "idx_projects_name", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "projects_name_unique": { + "name": "projects_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.search_snapshots": { + "name": "search_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_search_snapshots_created_at": { + "name": "idx_search_snapshots_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "search_snapshots_fingerprint_unique": { + "name": "search_snapshots_fingerprint_unique", + "nullsNotDistinct": false, + "columns": [ + "fingerprint" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.similar_media": { + "name": "similar_media", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "media1_id": { + "name": "media1_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "media2_id": { + "name": "media2_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "similarity_score": { + "name": "similarity_score", + "type": "real", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'perceptual'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_similar_media_score": { + "name": "idx_similar_media_score", + "columns": [ + { + "expression": "similarity_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "similar_media_media1_id_media_id_fk": { + "name": "similar_media_media1_id_media_id_fk", + "tableFrom": "similar_media", + "tableTo": "media", + "columnsFrom": [ + "media1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "similar_media_media2_id_media_id_fk": { + "name": "similar_media_media2_id_media_id_fk", + "tableFrom": "similar_media", + "tableTo": "media", + "columnsFrom": [ + "media2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "media1Id_media2Id_algorithm_unique": { + "name": "media1Id_media2Id_algorithm_unique", + "nullsNotDistinct": false, + "columns": [ + "media1_id", + "media2_id", + "algorithm" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attribute": { + "name": "attribute", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "author_id": { + "name": "author_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_tags_author_id": { + "name": "idx_tags_author_id", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tags_author_id_authors_id_fk": { + "name": "tags_author_id_authors_id_fk", + "tableFrom": "tags", + "tableTo": "authors", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.view_history": { + "name": "view_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "media_id": { + "name": "media_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "viewed_at": { + "name": "viewed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": { + "view_history_media_id_media_id_fk": { + "name": "view_history_media_id_media_id_fk", + "tableFrom": "view_history", + "tableTo": "media", + "columnsFrom": [ + "media_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.author_platform": { + "name": "author_platform", + "schema": "public", + "values": [ + "twitter", + "pixiv-fanbox", + "danbooru" + ] + }, + "public.job_status": { + "name": "job_status", + "schema": "public", + "values": [ + "pending", + "in_progress", + "completed", + "failed", + "cancelled" + ] + }, + "public.media_organization_status": { + "name": "media_organization_status", + "schema": "public", + "values": [ + "active", + "archived", + "deleted" + ] + }, + "public.media_region_kind": { + "name": "media_region_kind", + "schema": "public", + "values": [ + "full", + "person", + "manual" + ] + }, + "public.media_relation_type": { + "name": "media_relation_type", + "schema": "public", + "values": [ + "variant", + "version", + "page", + "derivative", + "edit", + "source" + ] + }, + "public.media_source_sync_status": { + "name": "media_source_sync_status", + "schema": "public", + "values": [ + "idle", + "syncing", + "error" + ] + }, + "public.media_source_type": { + "name": "media_source_type", + "schema": "public", + "values": [ + "local", + "sftp", + "s3" + ] + }, + "public.media_sync_status": { + "name": "media_sync_status", + "schema": "public", + "values": [ + "synced", + "pending", + "failed" + ] + }, + "public.media_type": { + "name": "media_type", + "schema": "public", + "values": [ + "image", + "video", + "audio" + ] + }, + "public.tag_type": { + "name": "tag_type", + "schema": "public", + "values": [ + "positive", + "negative" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json index 029ca5819..8e9db3b5e 100644 --- a/apps/server/drizzle/meta/_journal.json +++ b/apps/server/drizzle/meta/_journal.json @@ -183,6 +183,13 @@ "when": 1786574384357, "tag": "0025_left_doctor_octopus", "breakpoints": true + }, + { + "idx": 26, + "version": "7", + "when": 1787235852419, + "tag": "0026_nebulous_metal_master", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/server/public/openapi.json b/apps/server/public/openapi.json index 735937027..9cfed5c52 100644 --- a/apps/server/public/openapi.json +++ b/apps/server/public/openapi.json @@ -19,6 +19,10 @@ "name": "Media", "description": "Operations for managing media files and their metadata" }, + { + "name": "Search Snapshots", + "description": "Operations for storing and restoring immutable search states used by browser history" + }, { "name": "Tags", "description": "Operations for managing tags and tag associations" @@ -10239,6 +10243,260 @@ } } } + }, + "/searchSnapshots/capture": { + "post": { + "operationId": "searchSnapshots.capture", + "summary": "検索状態を履歴スナップショットとして保存", + "description": "ブラウザ履歴から復元できる検索条件を保存し、スナップショットIDを返します。", + "tags": [ + "Search Snapshots" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "state": { + "$ref": "#/components/schemas/SearchSnapshotState" + } + }, + "required": [ + "state" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "id" + ] + } + } + } + } + } + } + }, + "/searchSnapshots/get": { + "post": { + "operationId": "searchSnapshots.get", + "summary": "検索履歴スナップショット取得", + "description": "スナップショットIDから保存済みの検索条件を取得します。", + "tags": [ + "Search Snapshots" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchSnapshot" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "SearchSnapshotState": { + "type": "object", + "description": "検索結果を再現するための条件。ページング、スクロール位置、ユーザープリセットIDは含みません。", + "properties": { + "mode": { + "enum": [ + "simple", + "pro", + "vector" + ], + "type": "string" + }, + "searchQuery": { + "type": "string" + }, + "selectedTags": { + "type": "array", + "items": { + "type": "string" + } + }, + "excludeTags": { + "type": "array", + "items": { + "type": "string" + } + }, + "tagMode": { + "enum": [ + "and", + "or" + ], + "type": "string" + }, + "selectedSource": { + "type": "string" + }, + "selectedProjects": { + "type": "array", + "items": { + "type": "string" + } + }, + "selectedIps": { + "type": "array", + "items": { + "type": "string" + } + }, + "selectedCharacters": { + "type": "array", + "items": { + "type": "string" + } + }, + "selectedAuthors": { + "type": "array", + "items": { + "type": "string" + } + }, + "advancedCondition": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "similarityAnchorMediaId": { + "anyOf": [ + { + "type": "string", + "format": "uuid" + }, + { + "type": "null" + } + ] + }, + "similarityTopK": { + "enum": [ + 20, + 50, + 100 + ], + "type": "integer" + }, + "limit": { + "type": "number" + }, + "sortBy": { + "enum": [ + "date", + "name", + "size", + "rating", + "viewCount" + ], + "type": "string" + }, + "sortOrder": { + "enum": [ + "asc", + "desc" + ], + "type": "string" + } + }, + "required": [ + "mode", + "searchQuery", + "selectedTags", + "excludeTags", + "tagMode", + "selectedSource", + "selectedProjects", + "selectedIps", + "selectedCharacters", + "selectedAuthors", + "advancedCondition", + "similarityAnchorMediaId", + "similarityTopK", + "limit", + "sortBy", + "sortOrder" + ] + }, + "SearchSnapshot": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "version": { + "const": 1, + "type": "integer" + }, + "fingerprint": { + "type": "string" + }, + "state": { + "$ref": "#/components/schemas/SearchSnapshotState" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "version", + "fingerprint", + "state", + "createdAt" + ] + } } } } diff --git a/apps/server/src/infrastructure/api/app-router.ts b/apps/server/src/infrastructure/api/app-router.ts index af6c032cc..d0ea8ecda 100644 --- a/apps/server/src/infrastructure/api/app-router.ts +++ b/apps/server/src/infrastructure/api/app-router.ts @@ -13,6 +13,7 @@ import { jobsRouter } from "~/infrastructure/api/routers/jobs-router"; import { mediaRouter } from "~/infrastructure/api/routers/media-router"; import { presetsRouter } from "~/infrastructure/api/routers/presets-router"; import { projectsRouter } from "~/infrastructure/api/routers/projects-router"; +import { searchSnapshotsRouter } from "~/infrastructure/api/routers/search-snapshots-router"; import { sourcesRouter } from "~/infrastructure/api/routers/sources-router"; import { tagsRouter } from "~/infrastructure/api/routers/tags-router"; import { thumbnailsRouter } from "~/infrastructure/api/routers/thumbnails-router"; @@ -44,6 +45,7 @@ export const appRouter = appRouterImplementer.router({ utils: utilsRouter, config: configRouter, presets: presetsRouter, + searchSnapshots: searchSnapshotsRouter, }); export type AppRouter = typeof appRouter; diff --git a/apps/server/src/infrastructure/api/clients/search-history-client.ts b/apps/server/src/infrastructure/api/clients/search-history-client.ts new file mode 100644 index 000000000..751fd6541 --- /dev/null +++ b/apps/server/src/infrastructure/api/clients/search-history-client.ts @@ -0,0 +1,6 @@ +import type { SearchSnapshotsOrpcLike } from "@solid-imager/core/domain/contract/search-snapshots-client"; +import { orpc } from "~/infrastructure/api-clients/orpc-client"; + +export const SearchHistoryClient: SearchSnapshotsOrpcLike = { + searchSnapshots: orpc.searchSnapshots, +}; diff --git a/apps/server/src/infrastructure/api/openapi-tags.ts b/apps/server/src/infrastructure/api/openapi-tags.ts index bd8cd9f02..753a1a6cb 100644 --- a/apps/server/src/infrastructure/api/openapi-tags.ts +++ b/apps/server/src/infrastructure/api/openapi-tags.ts @@ -12,6 +12,11 @@ export const openApiTags = [ name: "Media", description: "Operations for managing media files and their metadata", }, + { + name: "Search Snapshots", + description: + "Operations for storing and restoring immutable search states used by browser history", + }, { name: "Tags", description: "Operations for managing tags and tag associations", diff --git a/apps/server/src/infrastructure/api/routers/search-snapshots-router.ts b/apps/server/src/infrastructure/api/routers/search-snapshots-router.ts new file mode 100644 index 000000000..ff45f7b26 --- /dev/null +++ b/apps/server/src/infrastructure/api/routers/search-snapshots-router.ts @@ -0,0 +1,13 @@ +import { implement } from "@orpc/server"; +import { searchSnapshotsContract } from "@solid-imager/core/domain/contract/search-snapshots.contract"; +import { SearchSnapshotService } from "~/infrastructure/services/search-snapshot-service"; + +const os = implement(searchSnapshotsContract); + +export const searchSnapshotsRouter = os.router({ + capture: os.capture.handler(async ({ input }) => { + const snapshot = await SearchSnapshotService.capture(input.state); + return { id: snapshot.id }; + }), + get: os.get.handler(async ({ input }) => SearchSnapshotService.get(input.id)), +}); diff --git a/apps/server/src/infrastructure/repositories/search-snapshot-repository.ts b/apps/server/src/infrastructure/repositories/search-snapshot-repository.ts new file mode 100644 index 000000000..bba0bb8d5 --- /dev/null +++ b/apps/server/src/infrastructure/repositories/search-snapshot-repository.ts @@ -0,0 +1,6 @@ +import type { SearchSnapshotRepository as SearchSnapshotRepositoryPort } from "@solid-imager/core/domain/repositories/search-snapshot-repository"; +import { createSearchSnapshotRepository } from "@solid-imager/db/repositories/search-snapshot-repository"; +import { getExecutor } from "~/infrastructure/db/executor"; + +export const SearchSnapshotRepository: SearchSnapshotRepositoryPort = + createSearchSnapshotRepository(getExecutor); diff --git a/apps/server/src/infrastructure/services/search-snapshot-service.ts b/apps/server/src/infrastructure/services/search-snapshot-service.ts new file mode 100644 index 000000000..93fabda75 --- /dev/null +++ b/apps/server/src/infrastructure/services/search-snapshot-service.ts @@ -0,0 +1,21 @@ +import type { ISearchSnapshotService } from "@solid-imager/application"; +import { createSearchSnapshotService } from "@solid-imager/application/services/search-snapshot-service"; +import type { SearchSnapshotRepository as SearchSnapshotRepositoryPort } from "@solid-imager/core/domain/repositories/search-snapshot-repository"; +import { SearchSnapshotRepository } from "~/infrastructure/repositories/search-snapshot-repository"; + +let service: ISearchSnapshotService = createSearchSnapshotService( + SearchSnapshotRepository, +); + +export const setSearchSnapshotRepository = ( + repository: SearchSnapshotRepositoryPort, +) => { + service = createSearchSnapshotService(repository); +}; + +export const SearchSnapshotService = new Proxy({} as ISearchSnapshotService, { + get(_, property) { + const value = service[property as keyof ISearchSnapshotService]; + return typeof value === "function" ? value.bind(service) : value; + }, +}); diff --git a/apps/server/src/routes/search.tsx b/apps/server/src/routes/search.tsx index 99f01a148..29773ee19 100644 --- a/apps/server/src/routes/search.tsx +++ b/apps/server/src/routes/search.tsx @@ -1,17 +1,18 @@ import { Button } from "@solid-imager/ui/button"; -import { - persistSearchScrollPosition, - useCurrentSearchPersistence, -} from "@solid-imager/ui/hooks/use-current-search-persistence"; +import { persistSearchScrollPosition } from "@solid-imager/ui/hooks/use-current-search-persistence"; +import { useSearchHistoryPersistence } from "@solid-imager/ui/hooks/use-search-history-persistence"; import { useSearchPage } from "@solid-imager/ui/hooks/use-search-page"; import { createPresetClient } from "@solid-imager/ui/preset-client"; import { RouteDataPendingScreen } from "@solid-imager/ui/router-status"; import { SearchScreen } from "@solid-imager/ui/screens/search-screen"; +import { createSearchHistoryClient } from "@solid-imager/ui/search-history-client"; +import { searchHistoryQuerySchema } from "@solid-imager/ui/search-history-route"; import { createFileRoute } from "@tanstack/solid-router"; import { createSignal, onMount, Show } from "solid-js"; import { LegacyMediaGridItem } from "~/components/media/legacy-media-grid-item"; import { useMediaSourceEvents } from "~/hooks/use-media-source-events"; import { PresetClient as rawPresetClient } from "~/infrastructure/api/clients/preset-client"; +import { SearchHistoryClient as rawSearchHistoryClient } from "~/infrastructure/api/clients/search-history-client"; import { allAuthorsQueryOptions, allCharactersQueryOptions, @@ -32,6 +33,7 @@ import { } from "~/presentation/store/search-store"; export const Route = createFileRoute("/search")({ + validateSearch: searchHistoryQuerySchema, ssr: true, loader: async ({ context, preload }: RouteLoaderContext) => { if (preload && typeof window !== "undefined") { @@ -65,6 +67,7 @@ export const Route = createFileRoute("/search")({ const SEARCH_RESULTS_REFRESH_DEBOUNCE_MS = 300; const PresetClient = createPresetClient(rawPresetClient); +const SearchHistoryClient = createSearchHistoryClient(rawSearchHistoryClient); function SearchRouteBoundary() { const [isMounted, setIsMounted] = createSignal(false); @@ -92,7 +95,9 @@ function SearchRouteFallback() { } function SearchRoute() { - const isSearchStateRestored = useCurrentSearchPersistence("all"); + const searchHistory = useSearchHistoryPersistence("all", { + client: SearchHistoryClient, + }); const page = useSearchPage({ searchMedia, @@ -113,14 +118,18 @@ function SearchRoute() { scrollY: () => searchState.scrollY, setScrollY: (y) => { setSearchState("scrollY", y); - persistSearchScrollPosition("all", y); + persistSearchScrollPosition("all", y, { + historyEntryKey: searchHistory.historyEntryKey, + }); }, setOffset: (o) => setSearchState("offset", o), mode: () => searchState.mode, similarityAnchorMediaId: () => searchState.similarityAnchorMediaId, similarityTopK: () => searchState.similarityTopK, refreshDebounceMs: SEARCH_RESULTS_REFRESH_DEBOUNCE_MS, - isSearchStateRestored, + isSearchStateRestored: searchHistory.isRestored, + commitSearchHistory: searchHistory.commitNow, + historyEntryKey: searchHistory.historyEntryKey, enableVirtualization: true, }); diff --git a/apps/server/src/routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx b/apps/server/src/routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx index 0e3c0622e..a3121f815 100644 --- a/apps/server/src/routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx +++ b/apps/server/src/routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx @@ -1,9 +1,13 @@ import { SourceMediaScreen } from "@solid-imager/ui/screens/source-media-screen"; +import { createSearchHistoryClient } from "@solid-imager/ui/search-history-client"; import type { Accessor } from "solid-js"; import { LegacyMediaGridItem } from "~/components/media/legacy-media-grid-item"; import { UploadMediaModal } from "~/components/upload-media-modal"; +import { SearchHistoryClient as rawSearchHistoryClient } from "~/infrastructure/api/clients/search-history-client"; import { SourceMediaPageController } from "./source-media-page"; +const SearchHistoryClient = createSearchHistoryClient(rawSearchHistoryClient); + export function SourceMediaPage( props: { mediaSourceId?: Accessor } = {}, ) { @@ -11,6 +15,7 @@ export function SourceMediaPage( ( ; screenComponent: Component; uploadModalComponent: SourceMediaScreenProps["uploadModalComponent"]; persistenceSurface: SearchPersistenceSurface; + searchHistoryClient: SearchHistoryClient; renderItem: SourceMediaGridRenderer; bulkActionsClass: string; ssrGuard?: boolean; @@ -137,6 +138,7 @@ export function SourceMediaPageController( mediaSourceId={mediaSourceId} mediaSourceName={mediaSourceName} persistenceSurface={props.persistenceSurface} + searchHistoryClient={props.searchHistoryClient} transport={transport} presetClient={PresetClient} actions={{ diff --git a/apps/server/src/routes/sources/$mediaSourceId/components/v2-source-media-page.tsx b/apps/server/src/routes/sources/$mediaSourceId/components/v2-source-media-page.tsx index 6d47677b6..4d39c9cd6 100644 --- a/apps/server/src/routes/sources/$mediaSourceId/components/v2-source-media-page.tsx +++ b/apps/server/src/routes/sources/$mediaSourceId/components/v2-source-media-page.tsx @@ -1,15 +1,19 @@ import { V2SourceMediaScreen } from "@solid-imager/ui/screens/v2-source-media-screen"; +import { createSearchHistoryClient } from "@solid-imager/ui/search-history-client"; import { useLocation, useNavigate } from "@tanstack/solid-router"; import type { Accessor } from "solid-js"; import { ThumbnailImage } from "~/components/media/thumbnail-image"; import { V2MediaGridItem } from "~/components/media/v2-media-grid-item"; import { V2UploadMediaModal } from "~/components/v2-upload-media-modal"; +import { SearchHistoryClient as rawSearchHistoryClient } from "~/infrastructure/api/clients/search-history-client"; import { saveV2MediaContext } from "~/routes/v2/media-context"; import { SourceMediaPageController, type SourceMediaPageControllerProps, } from "./source-media-page"; +const SearchHistoryClient = createSearchHistoryClient(rawSearchHistoryClient); + export function V2SourceMediaPage(props: { mediaSourceId?: Accessor }) { const location = useLocation(); const navigate = useNavigate(); @@ -38,6 +42,7 @@ export function V2SourceMediaPage(props: { mediaSourceId?: Accessor }) { onOpenMediaDetail={onOpenMediaDetail} onPrepareMediaDetail={onPrepareMediaDetail} persistenceSurface="v2" + searchHistoryClient={SearchHistoryClient} bulkActionsClass="fixed bottom-[calc(1rem+env(safe-area-inset-bottom))] left-1/2 z-50 flex w-[calc(100%-2rem)] max-w-md -translate-x-1/2 flex-wrap items-center justify-center gap-2 rounded-md border border-[var(--v2-border)] bg-[var(--v2-surface)] px-3 py-3 sm:bottom-[calc(1.5rem+env(safe-area-inset-bottom))] sm:w-auto sm:max-w-none sm:flex-nowrap sm:gap-3 sm:px-4" renderItem={(media, options, onToggleSelect) => ( [ params.mediaSourceId, diff --git a/apps/server/src/routes/v2/components/v2-search-content.tsx b/apps/server/src/routes/v2/components/v2-search-content.tsx index 18f26fbf6..dc44f92e5 100644 --- a/apps/server/src/routes/v2/components/v2-search-content.tsx +++ b/apps/server/src/routes/v2/components/v2-search-content.tsx @@ -1,15 +1,15 @@ -import { - persistSearchScrollPosition, - useCurrentSearchPersistence, -} from "@solid-imager/ui/hooks/use-current-search-persistence"; +import { persistSearchScrollPosition } from "@solid-imager/ui/hooks/use-current-search-persistence"; +import { useSearchHistoryPersistence } from "@solid-imager/ui/hooks/use-search-history-persistence"; import { useSearchPage } from "@solid-imager/ui/hooks/use-search-page"; import { createPresetClient } from "@solid-imager/ui/preset-client"; import { V2SearchScreen } from "@solid-imager/ui/screens/v2-search-screen"; +import { createSearchHistoryClient } from "@solid-imager/ui/search-history-client"; import { useLocation, useNavigate } from "@tanstack/solid-router"; import { ThumbnailImage } from "~/components/media/thumbnail-image"; import { V2MediaGridItem } from "~/components/media/v2-media-grid-item"; import { useMediaSourceEvents } from "~/hooks/use-media-source-events"; import { PresetClient as rawPresetClient } from "~/infrastructure/api/clients/preset-client"; +import { SearchHistoryClient as rawSearchHistoryClient } from "~/infrastructure/api/clients/search-history-client"; import { allAuthorsQueryOptions, allCharactersQueryOptions, @@ -32,6 +32,7 @@ import { saveV2MediaContext } from "../media-context"; const SEARCH_RESULTS_REFRESH_DEBOUNCE_MS = 300; const V2_SEARCH_RESULTS_PER_PAGE = 200; const PresetClient = createPresetClient(rawPresetClient); +const SearchHistoryClient = createSearchHistoryClient(rawSearchHistoryClient); function rememberReturnPath(href: string): void { try { @@ -44,7 +45,8 @@ function rememberReturnPath(href: string): void { export default function V2SearchContent() { const location = useLocation(); const navigate = useNavigate(); - const isSearchStateRestored = useCurrentSearchPersistence("all", { + const searchHistory = useSearchHistoryPersistence("all", { + client: SearchHistoryClient, surface: "v2", }); const page = useSearchPage({ @@ -68,14 +70,19 @@ export default function V2SearchContent() { scrollY: () => searchState.scrollY, setScrollY: (value) => { setSearchState("scrollY", value); - persistSearchScrollPosition("all", value, { surface: "v2" }); + persistSearchScrollPosition("all", value, { + surface: "v2", + historyEntryKey: searchHistory.historyEntryKey, + }); }, setOffset: (value) => setSearchState("offset", value), mode: () => searchState.mode, similarityAnchorMediaId: () => searchState.similarityAnchorMediaId, similarityTopK: () => searchState.similarityTopK, refreshDebounceMs: SEARCH_RESULTS_REFRESH_DEBOUNCE_MS, - isSearchStateRestored, + isSearchStateRestored: searchHistory.isRestored, + commitSearchHistory: searchHistory.commitNow, + historyEntryKey: searchHistory.historyEntryKey, enableVirtualization: true, scrollContainerSelector: '[data-media-scroll="v2-search"]', }); diff --git a/apps/server/src/routes/v2/search.tsx b/apps/server/src/routes/v2/search.tsx index 2a1ea7152..c8865334b 100644 --- a/apps/server/src/routes/v2/search.tsx +++ b/apps/server/src/routes/v2/search.tsx @@ -1,3 +1,4 @@ +import { searchHistoryQuerySchema } from "@solid-imager/ui/search-history-route"; import { createFileRoute, lazyRouteComponent } from "@tanstack/solid-router"; const V2SearchRoute = lazyRouteComponent( @@ -5,6 +6,7 @@ const V2SearchRoute = lazyRouteComponent( ); export const Route = createFileRoute("/v2/search")({ + validateSearch: searchHistoryQuerySchema, ssr: false, pendingComponent: () => null, component: V2SearchRoute, diff --git a/apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx b/apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx index efa90631b..a23840030 100644 --- a/apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx +++ b/apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx @@ -1,7 +1,9 @@ +import { searchHistoryQuerySchema } from "@solid-imager/ui/search-history-route"; import { createFileRoute } from "@tanstack/solid-router"; import { V2SourceMediaPage } from "~/routes/sources/$mediaSourceId/components/v2-source-media-page"; export const Route = createFileRoute("/v2/sources/$mediaSourceId/")({ + validateSearch: searchHistoryQuerySchema, ssr: false, pendingComponent: () => null, remountDeps: ({ params }: { params: { mediaSourceId: string } }) => [ diff --git a/apps/server/src/tests/e2e/search-history.spec.ts b/apps/server/src/tests/e2e/search-history.spec.ts new file mode 100644 index 000000000..01632e297 --- /dev/null +++ b/apps/server/src/tests/e2e/search-history.spec.ts @@ -0,0 +1,77 @@ +import { + E2E_PRIMARY_FILE_NAME, + E2E_SIMILAR_FILE_NAME, + E2E_SIMILAR_MEDIA_ID, + mediaPath, +} from "./support/fixture"; +import { expect, test, waitForAppHydration } from "./support/test"; + +const snapshotUrl = /\/search\?search=[0-9a-f-]{36}$/; + +test("search results remain traversable across detail navigation", async ({ + page, +}) => { + await page.goto("/search"); + await expect( + page.getByRole("heading", { name: "メディア検索", exact: true }), + ).toBeVisible(); + await waitForAppHydration(page); + + const fileNameInput = page.getByPlaceholder("ファイル名を入力..."); + const searchButton = page.getByRole("button", { name: "検索", exact: true }); + + await fileNameInput.fill(E2E_PRIMARY_FILE_NAME); + await searchButton.click(); + await expect(page).toHaveURL(snapshotUrl); + await expect(fileNameInput).toHaveValue(E2E_PRIMARY_FILE_NAME); + await expect( + page.getByRole("link", { name: new RegExp(E2E_PRIMARY_FILE_NAME) }), + ).toBeVisible(); + const firstSearchUrl = page.url(); + + await fileNameInput.fill(E2E_SIMILAR_FILE_NAME); + await searchButton.click(); + await expect(page).toHaveURL(snapshotUrl); + await expect(page).not.toHaveURL(firstSearchUrl); + await expect(fileNameInput).toHaveValue(E2E_SIMILAR_FILE_NAME); + await expect( + page.getByRole("link", { name: new RegExp(E2E_SIMILAR_FILE_NAME) }), + ).toBeVisible(); + const secondSearchUrl = page.url(); + + await page + .getByRole("link", { name: new RegExp(E2E_SIMILAR_FILE_NAME) }) + .click(); + await expect(page).toHaveURL( + new RegExp(`${mediaPath(E2E_SIMILAR_MEDIA_ID)}/?$`), + ); + await expect( + page.getByRole("heading", { name: E2E_SIMILAR_FILE_NAME, exact: true }), + ).toBeVisible(); + + await page.goBack(); + await expect(page).toHaveURL(secondSearchUrl); + await expect(page.getByPlaceholder("ファイル名を入力...")).toHaveValue( + E2E_SIMILAR_FILE_NAME, + ); + + await page.goBack(); + await expect(page).toHaveURL(firstSearchUrl); + await expect(page.getByPlaceholder("ファイル名を入力...")).toHaveValue( + E2E_PRIMARY_FILE_NAME, + ); + await expect( + page.getByRole("link", { name: new RegExp(E2E_PRIMARY_FILE_NAME) }), + ).toBeVisible(); + + // A copied/bookmarked URL has no in-memory history state, so it must restore + // the same snapshot through the server-side lookup endpoint. + await page.goto(secondSearchUrl); + await waitForAppHydration(page); + await expect(page.getByPlaceholder("ファイル名を入力...")).toHaveValue( + E2E_SIMILAR_FILE_NAME, + ); + await expect( + page.getByRole("link", { name: new RegExp(E2E_SIMILAR_FILE_NAME) }), + ).toBeVisible(); +}); diff --git a/apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts b/apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts new file mode 100644 index 000000000..a402096c0 --- /dev/null +++ b/apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts @@ -0,0 +1,72 @@ +import { ResourceNotFoundError } from "@solid-imager/core/domain/errors"; +import type { SearchSnapshotRepository } from "@solid-imager/core/domain/repositories/search-snapshot-repository"; +import { + type SearchSnapshot, + searchSnapshotStateSchema, +} from "@solid-imager/core/domain/search/history"; +import { defaultState } from "@solid-imager/core/domain/search/schema"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { + SearchSnapshotService, + setSearchSnapshotRepository, +} from "~/infrastructure/services/search-snapshot-service"; + +const snapshotState = searchSnapshotStateSchema.parse({ + ...defaultState, + activePresetId: undefined, + offset: undefined, + scrollY: undefined, +}); + +const snapshot: SearchSnapshot = { + id: "11111111-1111-4111-8111-111111111111", + version: 1, + fingerprint: "fingerprint", + state: snapshotState, + createdAt: new Date("2026-08-20T00:00:00.000Z"), +}; + +describe("SearchSnapshotService", () => { + let repository: SearchSnapshotRepository; + + beforeEach(() => { + repository = { + get: vi.fn().mockResolvedValue(snapshot), + getByFingerprint: vi.fn(), + create: vi.fn().mockResolvedValue(snapshot), + }; + setSearchSnapshotRepository(repository); + }); + + it("captures a validated state and delegates deduplication to the repository", async () => { + const result = await SearchSnapshotService.capture(snapshotState); + + expect(result).toEqual(snapshot); + expect(repository.create).toHaveBeenCalledWith({ + fingerprint: expect.any(String), + state: snapshotState, + }); + }); + + it("generates the same fingerprint for equivalent object key order", async () => { + await SearchSnapshotService.capture(snapshotState); + const firstFingerprint = vi.mocked(repository.create).mock.calls[0]?.[0] + .fingerprint; + await SearchSnapshotService.capture({ + ...snapshotState, + selectedTags: [...snapshotState.selectedTags], + }); + const secondFingerprint = vi.mocked(repository.create).mock.calls[1]?.[0] + .fingerprint; + + expect(secondFingerprint).toBe(firstFingerprint); + }); + + it("throws a typed not-found error", async () => { + vi.mocked(repository.get).mockResolvedValue(null); + + await expect( + SearchSnapshotService.get("22222222-2222-4222-8222-222222222222"), + ).rejects.toThrow(ResourceNotFoundError); + }); +}); diff --git a/apps/tauri/src/infrastructure/api/clients/search-history-client.ts b/apps/tauri/src/infrastructure/api/clients/search-history-client.ts new file mode 100644 index 000000000..e2094a4f8 --- /dev/null +++ b/apps/tauri/src/infrastructure/api/clients/search-history-client.ts @@ -0,0 +1,6 @@ +import type { SearchSnapshotsOrpcLike } from "@solid-imager/core/domain/contract/search-snapshots-client"; +import { client } from "~/orpc-client"; + +export const SearchHistoryClient: SearchSnapshotsOrpcLike = { + searchSnapshots: client.searchSnapshots, +}; diff --git a/apps/tauri/src/routes/search.tsx b/apps/tauri/src/routes/search.tsx index 244a1ef88..1961ea3d1 100644 --- a/apps/tauri/src/routes/search.tsx +++ b/apps/tauri/src/routes/search.tsx @@ -1,13 +1,16 @@ import { Button } from "@solid-imager/ui/button"; import { persistSearchScrollPosition } from "@solid-imager/ui/hooks/use-current-search-persistence"; +import { useSearchHistoryPersistence } from "@solid-imager/ui/hooks/use-search-history-persistence"; import { useSearchPage } from "@solid-imager/ui/hooks/use-search-page"; import { createPresetClient } from "@solid-imager/ui/preset-client"; import { SearchScreen } from "@solid-imager/ui/screens/search-screen"; +import { createSearchHistoryClient } from "@solid-imager/ui/search-history-client"; +import { searchHistoryQuerySchema } from "@solid-imager/ui/search-history-route"; import { createFileRoute } from "@tanstack/solid-router"; import { MediaGridItem } from "~/components/media/media-grid-item"; -import { useCurrentSearchPersistence } from "~/hooks/use-current-search-persistence"; import { useMediaSourceEvents } from "~/hooks/use-media-source-events"; import { PresetClient as rawPresetClient } from "~/infrastructure/api/clients/preset-client"; +import { SearchHistoryClient as rawSearchHistoryClient } from "~/infrastructure/api/clients/search-history-client"; import { searchMedia, searchSimilar, @@ -27,6 +30,7 @@ import { } from "~/queries"; export const Route = createFileRoute("/search")({ + validateSearch: searchHistoryQuerySchema, loader: ({ context }) => { void context.queryClient.prefetchQuery(tagsQueryOptions()); void context.queryClient.prefetchQuery(mediaSourcesQueryOptions()); @@ -41,9 +45,12 @@ export const Route = createFileRoute("/search")({ const SEARCH_RESULTS_REFRESH_DEBOUNCE_MS = 300; const PresetClient = createPresetClient(rawPresetClient); +const SearchHistoryClient = createSearchHistoryClient(rawSearchHistoryClient); function SearchRoute() { - const isSearchStateRestored = useCurrentSearchPersistence("all"); + const searchHistory = useSearchHistoryPersistence("all", { + client: SearchHistoryClient, + }); const page = useSearchPage({ searchMedia, @@ -64,14 +71,18 @@ function SearchRoute() { scrollY: () => searchState.scrollY, setScrollY: (y) => { setSearchState("scrollY", y); - persistSearchScrollPosition("all", y); + persistSearchScrollPosition("all", y, { + historyEntryKey: searchHistory.historyEntryKey, + }); }, setOffset: (o) => setSearchState("offset", o), mode: () => searchState.mode, similarityAnchorMediaId: () => searchState.similarityAnchorMediaId, similarityTopK: () => searchState.similarityTopK, refreshDebounceMs: SEARCH_RESULTS_REFRESH_DEBOUNCE_MS, - isSearchStateRestored, + isSearchStateRestored: searchHistory.isRestored, + commitSearchHistory: searchHistory.commitNow, + historyEntryKey: searchHistory.historyEntryKey, enableVirtualization: true, }); diff --git a/apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx b/apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx index d618e61b5..77ed03a33 100644 --- a/apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx +++ b/apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx @@ -1,6 +1,7 @@ import { useSourceRootPath } from "@solid-imager/ui/hooks/use-source-root-path"; import { createPresetClient } from "@solid-imager/ui/preset-client"; import { SourceMediaScreen } from "@solid-imager/ui/screens/source-media-screen"; +import { createSearchHistoryClient } from "@solid-imager/ui/search-history-client"; import { SourceMediaPage as SourceMediaPageComponent } from "@solid-imager/ui/source-media-page"; import { useParams } from "@tanstack/solid-router"; import { MediaGridItem } from "~/components/media/media-grid-item"; @@ -8,6 +9,7 @@ import { MoveCopyMediaDialog } from "~/components/media/move-copy-media-dialog"; import { UploadMediaModal } from "~/components/upload-media-modal"; import { createTauriTransport } from "~/hooks/use-media-source-events"; import { PresetClient as rawPresetClient } from "~/infrastructure/api/clients/preset-client"; +import { SearchHistoryClient as rawSearchHistoryClient } from "~/infrastructure/api/clients/search-history-client"; import { copyMedia, deleteMedia, @@ -39,6 +41,7 @@ import { } from "~/queries"; const presetClient = createPresetClient(rawPresetClient); +const searchHistoryClient = createSearchHistoryClient(rawSearchHistoryClient); export function SourceMediaPage() { const params = useParams({ from: "/sources/$mediaSourceId/" }); @@ -54,6 +57,7 @@ export function SourceMediaPage() { screenComponent={SourceMediaScreen} transport={transport} presetClient={presetClient} + searchHistoryClient={searchHistoryClient} actions={{ searchMedia, uploadMedia: (sourceId, file, opts) => diff --git a/apps/tauri/src/routes/sources/$mediaSourceId/index.tsx b/apps/tauri/src/routes/sources/$mediaSourceId/index.tsx index f9d638dc2..d84f0c71d 100644 --- a/apps/tauri/src/routes/sources/$mediaSourceId/index.tsx +++ b/apps/tauri/src/routes/sources/$mediaSourceId/index.tsx @@ -1,3 +1,4 @@ +import { searchHistoryQuerySchema } from "@solid-imager/ui/search-history-route"; import { createFileRoute } from "@tanstack/solid-router"; import { allAuthorsQueryOptions, @@ -10,6 +11,7 @@ import { import { SourceMediaPage } from "./components/source-media-page"; export const Route = createFileRoute("/sources/$mediaSourceId/")({ + validateSearch: searchHistoryQuerySchema, loader: ({ context }) => { void context.queryClient.prefetchQuery(tagsQueryOptions()); void context.queryClient.prefetchQuery(allProjectsQueryOptions()); diff --git a/docs/architecture/indexion-dependency-graph-01-apps-and-core.md b/docs/architecture/indexion-dependency-graph-01-apps-and-core.md index 4b77d69c3..d16891908 100644 --- a/docs/architecture/indexion-dependency-graph-01-apps-and-core.md +++ b/docs/architecture/indexion-dependency-graph-01-apps-and-core.md @@ -82,213 +82,225 @@ graph LR N77[apps/tauri/src/infrastructure/api-clients/thumbnails-api.ts] N78[apps/tauri/src/infrastructure/api/clients/preset-client.ts] N79[npm:@solid-imager/core/domain/contract/presets-client] - N80[apps/tauri/src/infrastructure/db/persistence.ts] - N81[apps/tauri/node_modules/@tauri-apps/plugin-sql/dist-js/index.cjs] - N82[apps/tauri/src/infrastructure/media/thumbnail-runtime.ts] - N83[npm:~/infrastructure/tauri-fetch-helpers] - N84[apps/tauri/src/infrastructure/tauri-fetch-helpers.ts] - N85[apps/tauri/node_modules/@tauri-apps/plugin-http/dist-js/index.cjs] - N86[apps/tauri/src/infrastructure/api-base.ts] - N87[apps/tauri/src/orpc-client.ts] - N88[apps/tauri/node_modules/@solid-imager/client/src/index.ts] - N89[npm:@solid-imager/core/domain/contract] - N90[apps/tauri/src/queries/index.ts] - N91[apps/tauri/node_modules/@orpc/solid-query/dist/index.d.mts] - N92[npm:@solid-imager/ui/query-options] - N93[apps/tauri/src/routes/$.tsx] - N94[npm:@solid-imager/ui/screens/not-found-screen] - N95[apps/tauri/src/routes/__root.tsx] - N96[npm:@solid-imager/ui/toast] - N97[npm:~/components/nav] - N98[apps/tauri/src/routes/about.tsx] - N99[npm:@solid-imager/ui/badge] - N100[apps/tauri/src/routes/config.tsx] - N101[npm:@solid-imager/ui/query-state] - N102[npm:@solid-imager/ui/screens/legacy-config-state-screen] - N103[apps/tauri/node_modules/@tanstack/solid-query/build/index.cjs] - N104[npm:~/queries] - N105[apps/tauri/src/routes/index.tsx] - N106[npm:@solid-imager/ui/button] - N107[apps/tauri/src/routes/search.tsx] - N108[npm:@solid-imager/ui/hooks/use-current-search-persistence] - N109[npm:@solid-imager/ui/hooks/use-search-page] - N110[npm:@solid-imager/ui/preset-client] - N111[npm:@solid-imager/ui/screens/search-screen] - N112[npm:~/components/media/media-grid-item] - N113[npm:~/hooks/use-current-search-persistence] - N114[npm:~/hooks/use-media-source-events] - N115[npm:~/infrastructure/api/clients/preset-client] - N116[apps/tauri/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] - N117[npm:@solid-imager/ui/hooks/use-source-root-path] - N118[npm:@solid-imager/ui/screens/legacy-media-detail-screen] - N119[npm:~/components/media/media-sidebar] - N120[npm:~/components/media/media-viewer] - N121[apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx] - N122[npm:@solid-imager/ui/screens/source-media-screen] - N123[npm:@solid-imager/ui/source-media-page] - N124[npm:~/components/media/move-copy-media-dialog] - N125[npm:~/components/upload-media-modal] - N126[apps/tauri/src/routes/sources/$mediaSourceId/index.tsx] - N127[apps/tauri/src/routes/sources/index.tsx] - N128[npm:@solid-imager/ui/hooks/use-sources-events] - N129[npm:@solid-imager/ui/hooks/use-sources-page] - N130[npm:@solid-imager/ui/legacy-source-form-modal] - N131[npm:@solid-imager/ui/screens/sources-screen] - N132[npm:@solid-imager/ui/source-card] - N133[npm:@solid-imager/ui/source-delete-modal] - N134[apps/tauri/node_modules/@tanstack/solid-db/dist/esm/index.js] - N135[npm:~/collections] - N136[npm:~/collections/query-keys] - N137[apps/tauri/src/routes/v2/$.tsx] - N138[apps/tauri/src/routeTree.gen.ts] - N139[apps/tauri/src/routes/manager.tsx] - N140[apps/tauri/src/routes/v2.tsx] - N141[apps/xtracter/src/api.ts] - N142[apps/xtracter/node_modules/@solid-imager/client/src/index.ts] - N143[apps/xtracter/src/background/index.ts] - N144[npm:@core/domain/media/utils/filename-utils] - N145[npm:@core/domain/sources/schemas] - N146[npm:@ext/api] - N147[apps/xtracter/src/content/danbooru.ts] - N148[npm:@ext/schema] - N149[apps/xtracter/src/utils/dom-utils.ts] - N150[apps/xtracter/src/content/index.ts] - N151[apps/xtracter/src/content/fanbox.ts] - N152[apps/xtracter/src/content/twitter.ts] - N153[apps/xtracter/src/content/twitter.test.ts] - N154[node_modules/vitest/dist/index.js] - N155[apps/xtracter/src/popup/index.html] - N156[url:en] - N157[url:UTF-8] - N158[url:viewport] - N159[url:width=device-width, initial-scale=1.0] - N160[url:root] - N161[url:module] - N162[url:index.tsx] - N163[apps/xtracter/src/popup/index.tsx] - N164[apps/xtracter/node_modules/solid-js/types/index.d.ts] - N165[apps/xtracter/node_modules/solid-js/web/types/index.d.ts] - N166[apps/xtracter/src/schema.ts] - N167[apps/xtracter/node_modules/zod/index.d.cts] - N168[packages/application/src/ports/media-service.ts] - N169[npm:@solid-imager/core/domain/interfaces/transaction-manager] - N170[packages/application/src/ports/media-processing-service.ts] - N171[packages/application/src/ports/search-service.ts] - N172[packages/application/src/services/ip-service.ts] - N173[npm:@solid-imager/core/domain/ips/schemas] - N174[npm:@solid-imager/core/domain/repositories/ip-repository] - N175[packages/application/src/ports/ip-service.ts] - N176[packages/application/src/services/media-processing-service.ts] - N177[npm:@solid-imager/core/domain/characters/schemas] - N178[packages/application/src/services/media-query-service.ts] - N179[packages/application/node_modules/@solid-imager/core/src/index.ts] - N180[npm:@solid-imager/core/domain/errors] - N181[packages/application/src/services/media-service.ts] - N182[packages/application/src/services/media-transfer-service.ts] - N183[packages/application/src/services/media-upload-service.ts] - N184[packages/application/src/services/tagging-service.ts] - N185[npm:@solid-imager/core/domain/interfaces/ai-client] - N186[npm:@solid-imager/core/domain/repositories/character-repository] - N187[npm:@solid-imager/core/domain/repositories/media-repository] - N188[npm:@solid-imager/core/domain/repositories/source-repository] - N189[npm:@solid-imager/core/domain/repositories/tag-repository] - N190[npm:@solid-imager/core/domain/sources/events] - N191[npm:@solid-imager/core/domain/tagging/constants] - N192[packages/application/src/services/user-service.ts] - N193[npm:@solid-imager/core/domain/repositories/user-repository] - N194[packages/application/src/utils/hash-utils.ts] - N195[npm:node:crypto] - N196[packages/client/src/create-client.ts] - N197[packages/client/node_modules/@orpc/client/dist/index.d.mts] - N198[npm:@orpc/client/fetch] - N199[packages/client/node_modules/@orpc/contract/dist/index.d.mts] - N200[packages/client/src/api-error.ts] - N201[packages/client/src/api-error.test.ts] - N202[packages/client/node_modules/vitest/dist/index.js] - N203[packages/client/src/create-client.test.ts] - N204[packages/core/src/domain/authors/schemas.ts] - N205[packages/core/node_modules/zod/index.d.cts] - N206[packages/core/src/domain/media/schemas.ts] - N207[packages/core/src/domain/categories/schemas.ts] - N208[packages/core/src/domain/characters/schemas.ts] - N209[packages/core/src/domain/collections/schemas.ts] - N210[packages/core/src/domain/config/config-schema.ts] - N211[packages/core/src/domain/contract/ai.contract.ts] - N212[packages/core/node_modules/@orpc/contract/dist/index.d.mts] - N213[packages/core/src/domain/contract/authors.contract.ts] - N214[packages/core/src/domain/contract/categories.contract.ts] - N215[packages/core/src/domain/contract/characters.contract.ts] - N216[packages/core/src/domain/contract/config.contract.ts] - N217[packages/core/src/domain/contract/directories.contract.ts] - N218[packages/core/src/domain/contract/downloads.contract.ts] - N219[packages/core/src/domain/contract/imports.contract.ts] - N220[packages/core/src/domain/contract/index.ts] - N221[packages/core/src/domain/contract/ips.contract.ts] - N222[packages/core/src/domain/contract/jobs.contract.ts] - N223[packages/core/src/domain/contract/media.contract.ts] - N224[packages/core/src/domain/contract/presets.contract.ts] - N225[packages/core/src/domain/contract/projects.contract.ts] - N226[packages/core/src/domain/contract/sources.contract.ts] - N227[packages/core/src/domain/contract/tags.contract.ts] - N228[packages/core/src/domain/contract/thumbnails.contract.ts] - N229[packages/core/src/domain/contract/utils.contract.ts] - N230[packages/core/src/domain/ips/schemas.ts] - N231[packages/core/src/domain/jobs/schemas.ts] - N232[packages/core/src/domain/sources/events.ts] - N233[packages/core/src/domain/contract/presets-client.ts] - N234[packages/core/src/domain/events/media-source-events.ts] - N235[packages/core/src/domain/media/upload-schemas.ts] - N236[packages/core/src/domain/media/utils/filename-utils.ts] - N237[packages/core/src/domain/media/utils/metadata-utils.ts] - N238[npm:@/domain/media/schemas] - N239[packages/core/src/domain/projects/schemas.ts] - N240[packages/core/src/domain/repositories/author-repository.ts] - N241[npm:@/domain/interfaces/transaction-manager] - N242[packages/core/src/domain/repositories/authors-repository.ts] - N243[npm:@/domain/authors/schemas] - N244[packages/core/src/domain/repositories/category-repository.ts] - N245[npm:@/domain/categories/schemas] - N246[packages/core/src/domain/repositories/ip-repository.ts] - N247[npm:@/domain/ips/schemas] - N248[packages/core/src/domain/repositories/media-repository.ts] - N249[packages/core/src/domain/repositories/project-repository.ts] - N250[packages/core/src/domain/repositories/source-repository.ts] - N251[packages/core/src/domain/repositories/tag-repository.ts] - N252[npm:@/domain/tags/schemas] - N253[packages/core/src/domain/repositories/user-repository.ts] - N254[npm:@/domain/users/schemas] - N255[packages/core/src/domain/search/schema.ts] - N256[packages/core/src/domain/services/storage-service.ts] - N257[npm:@/domain/media/upload-schemas] - N258[packages/core/src/domain/shared/schemas.ts] - N259[packages/core/src/domain/thumbnails/schemas.ts] - N260[packages/core/src/domain/sources/schemas.ts] - N261[packages/core/src/domain/sources/store.ts] - N262[packages/core/node_modules/solid-js/store/types/index.d.ts] - N263[packages/core/src/domain/tagging/schemas.ts] - N264[packages/core/src/domain/tags/extractor.ts] - N265[packages/core/src/utils/type-guards.ts] - N266[packages/core/src/domain/tags/schemas.ts] - N267[packages/core/src/domain/users/schemas.ts] - N268[packages/core/src/interfaces/config-service.ts] - N269[npm:@/domain/config/config-schema] - N270[packages/core/src/interfaces/media-storage.ts] - N271[packages/core/src/utils/deep-equal.ts] - N272[packages/db/src/repositories/author-repository.ts] - N273[packages/db/src/repositories/authors-repository.ts] - N274[npm:@solid-imager/core/domain/authors/schemas] - N275[packages/db/src/repositories/job-repository.ts] - N276[npm:@solid-imager/core/domain/jobs/schemas] - N277[packages/db/src/repositories/media-repository-utils.ts] - N278[packages/db/src/repositories/project-repository.ts] - N279[packages/db/src/repositories/job-repository.test.ts] - N280[packages/db/node_modules/vitest/dist/index.js] - N281[packages/db/src/types.ts] - N282[packages/db/src/schema.ts] - N283[npm:@solid-imager/core/domain/repositories/job-repository] - N284[packages/db/node_modules/drizzle-orm/index.d.ts] - N285[packages/db/node_modules/drizzle-orm/node-postgres/index.d.ts] - N286[packages/db/node_modules/drizzle-orm/pglite/index.d.ts] + N80[apps/tauri/src/infrastructure/api/clients/search-history-client.ts] + N81[npm:@solid-imager/core/domain/contract/search-snapshots-client] + N82[apps/tauri/src/infrastructure/db/persistence.ts] + N83[apps/tauri/node_modules/@tauri-apps/plugin-sql/dist-js/index.cjs] + N84[apps/tauri/src/infrastructure/media/thumbnail-runtime.ts] + N85[npm:~/infrastructure/tauri-fetch-helpers] + N86[apps/tauri/src/infrastructure/tauri-fetch-helpers.ts] + N87[apps/tauri/node_modules/@tauri-apps/plugin-http/dist-js/index.cjs] + N88[apps/tauri/src/infrastructure/api-base.ts] + N89[apps/tauri/src/orpc-client.ts] + N90[apps/tauri/node_modules/@solid-imager/client/src/index.ts] + N91[npm:@solid-imager/core/domain/contract] + N92[apps/tauri/src/queries/index.ts] + N93[apps/tauri/node_modules/@orpc/solid-query/dist/index.d.mts] + N94[npm:@solid-imager/ui/query-options] + N95[apps/tauri/src/routes/$.tsx] + N96[npm:@solid-imager/ui/screens/not-found-screen] + N97[apps/tauri/src/routes/__root.tsx] + N98[npm:@solid-imager/ui/toast] + N99[npm:~/components/nav] + N100[apps/tauri/src/routes/about.tsx] + N101[npm:@solid-imager/ui/badge] + N102[apps/tauri/src/routes/config.tsx] + N103[npm:@solid-imager/ui/query-state] + N104[npm:@solid-imager/ui/screens/legacy-config-state-screen] + N105[apps/tauri/node_modules/@tanstack/solid-query/build/index.cjs] + N106[npm:~/queries] + N107[apps/tauri/src/routes/index.tsx] + N108[npm:@solid-imager/ui/button] + N109[apps/tauri/src/routes/search.tsx] + N110[npm:@solid-imager/ui/hooks/use-current-search-persistence] + N111[npm:@solid-imager/ui/hooks/use-search-history-persistence] + N112[npm:@solid-imager/ui/hooks/use-search-page] + N113[npm:@solid-imager/ui/preset-client] + N114[npm:@solid-imager/ui/screens/search-screen] + N115[npm:@solid-imager/ui/search-history-client] + N116[npm:@solid-imager/ui/search-history-route] + N117[npm:~/components/media/media-grid-item] + N118[npm:~/hooks/use-media-source-events] + N119[npm:~/infrastructure/api/clients/preset-client] + N120[npm:~/infrastructure/api/clients/search-history-client] + N121[apps/tauri/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] + N122[npm:@solid-imager/ui/hooks/use-source-root-path] + N123[npm:@solid-imager/ui/screens/legacy-media-detail-screen] + N124[npm:~/components/media/media-sidebar] + N125[npm:~/components/media/media-viewer] + N126[apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx] + N127[npm:@solid-imager/ui/screens/source-media-screen] + N128[npm:@solid-imager/ui/source-media-page] + N129[npm:~/components/media/move-copy-media-dialog] + N130[npm:~/components/upload-media-modal] + N131[apps/tauri/src/routes/sources/$mediaSourceId/index.tsx] + N132[apps/tauri/src/routes/sources/index.tsx] + N133[npm:@solid-imager/ui/hooks/use-sources-events] + N134[npm:@solid-imager/ui/hooks/use-sources-page] + N135[npm:@solid-imager/ui/legacy-source-form-modal] + N136[npm:@solid-imager/ui/screens/sources-screen] + N137[npm:@solid-imager/ui/source-card] + N138[npm:@solid-imager/ui/source-delete-modal] + N139[apps/tauri/node_modules/@tanstack/solid-db/dist/esm/index.js] + N140[npm:~/collections] + N141[npm:~/collections/query-keys] + N142[apps/tauri/src/routes/v2/$.tsx] + N143[apps/tauri/src/routes/jobs.tsx] + N144[apps/tauri/src/routeTree.gen.ts] + N145[apps/tauri/src/routes/manager.tsx] + N146[apps/tauri/src/routes/v2.tsx] + N147[apps/xtracter/src/api.ts] + N148[apps/xtracter/node_modules/@solid-imager/client/src/index.ts] + N149[apps/xtracter/src/background/index.ts] + N150[npm:@core/domain/media/utils/filename-utils] + N151[npm:@core/domain/sources/schemas] + N152[npm:@ext/api] + N153[apps/xtracter/src/content/danbooru.ts] + N154[npm:@ext/schema] + N155[apps/xtracter/src/utils/dom-utils.ts] + N156[apps/xtracter/src/content/index.ts] + N157[apps/xtracter/src/content/fanbox.ts] + N158[apps/xtracter/src/content/twitter.ts] + N159[apps/xtracter/src/content/twitter.test.ts] + N160[node_modules/vitest/dist/index.js] + N161[apps/xtracter/src/popup/index.html] + N162[url:en] + N163[url:UTF-8] + N164[url:viewport] + N165[url:width=device-width, initial-scale=1.0] + N166[url:root] + N167[url:module] + N168[url:index.tsx] + N169[apps/xtracter/src/popup/index.tsx] + N170[apps/xtracter/node_modules/solid-js/types/index.d.ts] + N171[apps/xtracter/node_modules/solid-js/web/types/index.d.ts] + N172[apps/xtracter/src/schema.ts] + N173[apps/xtracter/node_modules/zod/index.d.cts] + N174[packages/application/src/ports/media-service.ts] + N175[npm:@solid-imager/core/domain/interfaces/transaction-manager] + N176[packages/application/src/ports/media-processing-service.ts] + N177[packages/application/src/ports/search-service.ts] + N178[packages/application/src/services/ip-service.ts] + N179[npm:@solid-imager/core/domain/ips/schemas] + N180[npm:@solid-imager/core/domain/repositories/ip-repository] + N181[packages/application/src/ports/ip-service.ts] + N182[packages/application/src/services/media-processing-service.ts] + N183[npm:@solid-imager/core/domain/characters/schemas] + N184[packages/application/src/services/media-query-service.ts] + N185[packages/application/node_modules/@solid-imager/core/src/index.ts] + N186[npm:@solid-imager/core/domain/errors] + N187[packages/application/src/services/media-service.ts] + N188[packages/application/src/services/media-transfer-service.ts] + N189[packages/application/src/services/media-upload-service.ts] + N190[packages/application/src/services/tagging-service.ts] + N191[npm:@solid-imager/core/domain/interfaces/ai-client] + N192[npm:@solid-imager/core/domain/repositories/character-repository] + N193[npm:@solid-imager/core/domain/repositories/media-repository] + N194[npm:@solid-imager/core/domain/repositories/source-repository] + N195[npm:@solid-imager/core/domain/repositories/tag-repository] + N196[npm:@solid-imager/core/domain/sources/events] + N197[npm:@solid-imager/core/domain/tagging/constants] + N198[packages/application/src/services/user-service.ts] + N199[npm:@solid-imager/core/domain/repositories/user-repository] + N200[packages/application/src/services/search-snapshot-service.ts] + N201[npm:node:crypto] + N202[npm:@solid-imager/core/domain/repositories/search-snapshot-repository] + N203[packages/application/src/utils/hash-utils.ts] + N204[packages/client/src/create-client.ts] + N205[packages/client/node_modules/@orpc/client/dist/index.d.mts] + N206[npm:@orpc/client/fetch] + N207[packages/client/node_modules/@orpc/contract/dist/index.d.mts] + N208[packages/client/src/api-error.ts] + N209[packages/client/src/api-error.test.ts] + N210[packages/client/node_modules/vitest/dist/index.js] + N211[packages/client/src/create-client.test.ts] + N212[packages/core/src/domain/authors/schemas.ts] + N213[packages/core/node_modules/zod/index.d.cts] + N214[packages/core/src/domain/media/schemas.ts] + N215[packages/core/src/domain/categories/schemas.ts] + N216[packages/core/src/domain/characters/schemas.ts] + N217[packages/core/src/domain/collections/schemas.ts] + N218[packages/core/src/domain/config/config-schema.ts] + N219[packages/core/src/domain/contract/ai.contract.ts] + N220[packages/core/node_modules/@orpc/contract/dist/index.d.mts] + N221[packages/core/src/domain/contract/authors.contract.ts] + N222[packages/core/src/domain/contract/categories.contract.ts] + N223[packages/core/src/domain/contract/characters.contract.ts] + N224[packages/core/src/domain/contract/config.contract.ts] + N225[packages/core/src/domain/contract/directories.contract.ts] + N226[packages/core/src/domain/contract/downloads.contract.ts] + N227[packages/core/src/domain/contract/imports.contract.ts] + N228[packages/core/src/domain/contract/index.ts] + N229[packages/core/src/domain/contract/ips.contract.ts] + N230[packages/core/src/domain/contract/jobs.contract.ts] + N231[packages/core/src/domain/contract/media.contract.ts] + N232[packages/core/src/domain/contract/presets.contract.ts] + N233[packages/core/src/domain/contract/projects.contract.ts] + N234[packages/core/src/domain/contract/search-snapshots.contract.ts] + N235[packages/core/src/domain/contract/sources.contract.ts] + N236[packages/core/src/domain/contract/tags.contract.ts] + N237[packages/core/src/domain/contract/thumbnails.contract.ts] + N238[packages/core/src/domain/contract/utils.contract.ts] + N239[packages/core/src/domain/ips/schemas.ts] + N240[packages/core/src/domain/jobs/schemas.ts] + N241[packages/core/src/domain/sources/events.ts] + N242[packages/core/src/domain/contract/presets-client.ts] + N243[packages/core/src/domain/contract/search-snapshots-client.ts] + N244[packages/core/src/domain/events/media-source-events.ts] + N245[packages/core/src/domain/media/upload-schemas.ts] + N246[packages/core/src/domain/media/utils/filename-utils.ts] + N247[packages/core/src/domain/media/utils/metadata-utils.ts] + N248[npm:@/domain/media/schemas] + N249[packages/core/src/domain/projects/schemas.ts] + N250[packages/core/src/domain/repositories/author-repository.ts] + N251[npm:@/domain/interfaces/transaction-manager] + N252[packages/core/src/domain/repositories/authors-repository.ts] + N253[npm:@/domain/authors/schemas] + N254[packages/core/src/domain/repositories/category-repository.ts] + N255[npm:@/domain/categories/schemas] + N256[packages/core/src/domain/repositories/ip-repository.ts] + N257[npm:@/domain/ips/schemas] + N258[packages/core/src/domain/repositories/media-repository.ts] + N259[packages/core/src/domain/repositories/project-repository.ts] + N260[packages/core/src/domain/repositories/source-repository.ts] + N261[packages/core/src/domain/repositories/tag-repository.ts] + N262[npm:@/domain/tags/schemas] + N263[packages/core/src/domain/repositories/user-repository.ts] + N264[npm:@/domain/users/schemas] + N265[packages/core/src/domain/search/schema.ts] + N266[packages/core/src/domain/search/history.ts] + N267[packages/core/src/domain/services/storage-service.ts] + N268[npm:@/domain/media/upload-schemas] + N269[packages/core/src/domain/shared/schemas.ts] + N270[packages/core/src/domain/thumbnails/schemas.ts] + N271[packages/core/src/domain/sources/schemas.ts] + N272[packages/core/src/domain/sources/store.ts] + N273[packages/core/node_modules/solid-js/store/types/index.d.ts] + N274[packages/core/src/domain/tagging/schemas.ts] + N275[packages/core/src/domain/tags/extractor.ts] + N276[packages/core/src/utils/type-guards.ts] + N277[packages/core/src/domain/tags/schemas.ts] + N278[packages/core/src/domain/users/schemas.ts] + N279[packages/core/src/interfaces/config-service.ts] + N280[npm:@/domain/config/config-schema] + N281[packages/core/src/interfaces/media-storage.ts] + N282[packages/core/src/utils/deep-equal.ts] + N283[packages/db/src/repositories/author-repository.ts] + N284[packages/db/src/repositories/authors-repository.ts] + N285[npm:@solid-imager/core/domain/authors/schemas] + N286[packages/db/src/repositories/job-repository.ts] + N287[npm:@solid-imager/core/domain/jobs/schemas] + N288[packages/db/src/repositories/media-repository-utils.ts] + N289[packages/db/src/repositories/project-repository.ts] + N290[packages/db/src/repositories/job-repository.test.ts] + N291[packages/db/node_modules/vitest/dist/index.js] + N292[packages/db/src/types.ts] + N293[packages/db/src/repositories/search-snapshot-repository.ts] + N294[packages/db/src/schema.ts] + N295[npm:@solid-imager/core/domain/repositories/job-repository] + N296[packages/db/node_modules/drizzle-orm/index.d.ts] + N297[packages/db/node_modules/drizzle-orm/node-postgres/index.d.ts] + N298[packages/db/node_modules/drizzle-orm/pglite/index.d.ts] N0 --> N1 N0 --> N2 N0 --> N3 @@ -419,285 +431,305 @@ graph LR N77 --> N52 N78 --> N79 N78 --> N19 - N80 --> N36 N80 --> N81 + N80 --> N19 + N82 --> N36 N82 --> N83 N84 --> N85 - N84 --> N86 - N87 --> N88 - N87 --> N89 - N87 --> N85 - N90 --> N91 - N32 --> N88 - N32 --> N92 - N93 --> N94 - N93 --> N27 - N95 --> N25 - N95 --> N26 + N86 --> N87 + N86 --> N88 + N89 --> N90 + N89 --> N91 + N89 --> N87 + N92 --> N93 + N32 --> N90 + N32 --> N94 N95 --> N96 N95 --> N27 - N95 --> N97 - N95 --> N38 - N98 --> N99 - N100 --> N92 + N97 --> N25 + N97 --> N26 + N97 --> N98 + N97 --> N27 + N97 --> N99 + N97 --> N38 N100 --> N101 - N100 --> N102 - N100 --> N103 - N100 --> N27 - N100 --> N52 - N100 --> N104 - N105 --> N99 - N105 --> N106 - N107 --> N106 + N102 --> N94 + N102 --> N103 + N102 --> N104 + N102 --> N105 + N102 --> N27 + N102 --> N52 + N102 --> N106 + N107 --> N101 N107 --> N108 - N107 --> N109 - N107 --> N110 - N107 --> N111 - N107 --> N27 - N107 --> N112 - N107 --> N113 - N107 --> N114 - N107 --> N115 - N116 --> N117 - N116 --> N92 - N116 --> N26 - N116 --> N118 - N116 --> N103 - N116 --> N27 - N116 --> N119 - N116 --> N120 - N116 --> N114 - N116 --> N104 - N121 --> N117 - N121 --> N110 + N109 --> N108 + N109 --> N110 + N109 --> N111 + N109 --> N112 + N109 --> N113 + N109 --> N114 + N109 --> N115 + N109 --> N116 + N109 --> N27 + N109 --> N117 + N109 --> N118 + N109 --> N119 + N109 --> N120 N121 --> N122 + N121 --> N94 + N121 --> N26 N121 --> N123 + N121 --> N105 N121 --> N27 - N121 --> N112 N121 --> N124 N121 --> N125 - N121 --> N114 - N121 --> N115 + N121 --> N118 + N121 --> N106 + N126 --> N122 + N126 --> N113 + N126 --> N127 + N126 --> N115 + N126 --> N128 N126 --> N27 - N127 --> N22 - N127 --> N48 - N127 --> N128 - N127 --> N129 - N127 --> N130 - N127 --> N101 - N127 --> N131 - N127 --> N132 - N127 --> N133 - N127 --> N134 - N127 --> N103 - N127 --> N27 - N127 --> N135 - N127 --> N136 - N127 --> N52 - N137 --> N94 - N137 --> N27 - N137 --> N28 - N138 --> N95 - N138 --> N105 - N138 --> N93 - N138 --> N98 - N138 --> N100 - N138 --> N139 - N138 --> N107 - N138 --> N140 - N138 --> N127 - N138 --> N137 - N138 --> N126 - N138 --> N116 - N141 --> N142 - N141 --> N89 - N143 --> N144 - N143 --> N145 - N143 --> N146 + N126 --> N117 + N126 --> N129 + N126 --> N130 + N126 --> N118 + N126 --> N119 + N126 --> N120 + N131 --> N116 + N131 --> N27 + N132 --> N22 + N132 --> N48 + N132 --> N133 + N132 --> N134 + N132 --> N135 + N132 --> N103 + N132 --> N136 + N132 --> N137 + N132 --> N138 + N132 --> N139 + N132 --> N105 + N132 --> N27 + N132 --> N140 + N132 --> N141 + N132 --> N52 + N142 --> N96 + N142 --> N27 + N142 --> N28 + N143 --> N90 + N144 --> N97 + N144 --> N107 + N144 --> N95 + N144 --> N100 + N144 --> N102 + N144 --> N143 + N144 --> N145 + N144 --> N109 + N144 --> N146 + N144 --> N132 + N144 --> N142 + N144 --> N131 + N144 --> N121 N147 --> N148 - N147 --> N149 - N150 --> N148 - N150 --> N147 - N150 --> N151 - N150 --> N152 - N152 --> N148 - N152 --> N149 - N151 --> N148 - N151 --> N149 + N147 --> N91 + N149 --> N150 + N149 --> N151 + N149 --> N152 N153 --> N154 - N153 --> N152 - N155 --> N156 - N155 --> N157 - N155 --> N158 - N155 --> N159 - N155 --> N160 - N155 --> N161 - N155 --> N162 - N163 --> N146 - N163 --> N148 - N163 --> N164 - N163 --> N165 - N166 --> N167 - N168 --> N169 - N170 --> N169 - N171 --> N54 + N153 --> N155 + N156 --> N154 + N156 --> N153 + N156 --> N157 + N156 --> N158 + N158 --> N154 + N158 --> N155 + N157 --> N154 + N157 --> N155 + N159 --> N160 + N159 --> N158 + N161 --> N162 + N161 --> N163 + N161 --> N164 + N161 --> N165 + N161 --> N166 + N161 --> N167 + N161 --> N168 + N169 --> N152 + N169 --> N154 + N169 --> N170 + N169 --> N171 N172 --> N173 - N172 --> N174 - N172 --> N175 - N176 --> N10 - N176 --> N177 - N176 --> N169 - N178 --> N10 + N174 --> N175 + N176 --> N175 + N177 --> N54 N178 --> N179 N178 --> N180 - N181 --> N169 + N178 --> N181 N182 --> N10 - N182 --> N179 - N182 --> N180 - N183 --> N10 - N183 --> N179 - N183 --> N180 + N182 --> N183 + N182 --> N175 N184 --> N10 N184 --> N185 N184 --> N186 - N184 --> N174 - N184 --> N187 - N184 --> N188 - N184 --> N189 - N184 --> N190 - N184 --> N22 - N184 --> N191 - N192 --> N193 - N194 --> N195 - N194 --> N9 - N194 --> N14 - N196 --> N197 - N196 --> N198 - N196 --> N199 - N196 --> N200 - N201 --> N202 - N201 --> N200 - N203 --> N202 - N203 --> N200 - N203 --> N196 + N187 --> N175 + N188 --> N10 + N188 --> N185 + N188 --> N186 + N189 --> N10 + N189 --> N185 + N189 --> N186 + N190 --> N10 + N190 --> N191 + N190 --> N192 + N190 --> N180 + N190 --> N193 + N190 --> N194 + N190 --> N195 + N190 --> N196 + N190 --> N22 + N190 --> N197 + N198 --> N199 + N200 --> N201 + N200 --> N186 + N200 --> N202 + N203 --> N201 + N203 --> N9 + N203 --> N14 N204 --> N205 N204 --> N206 - N207 --> N205 - N208 --> N205 - N209 --> N205 - N210 --> N205 - N211 --> N212 - N211 --> N205 - N213 --> N212 - N213 --> N204 - N214 --> N212 - N214 --> N205 - N214 --> N207 - N215 --> N212 - N215 --> N205 - N216 --> N212 - N216 --> N210 - N217 --> N212 - N217 --> N205 - N218 --> N212 - N218 --> N205 - N218 --> N206 - N219 --> N212 - N219 --> N205 - N220 --> N211 - N220 --> N213 - N220 --> N214 - N220 --> N215 - N220 --> N216 - N220 --> N217 - N220 --> N218 - N220 --> N219 - N220 --> N221 - N220 --> N222 - N220 --> N223 - N220 --> N224 - N220 --> N225 - N220 --> N226 - N220 --> N227 - N220 --> N228 - N220 --> N229 + N204 --> N207 + N204 --> N208 + N209 --> N210 + N209 --> N208 + N211 --> N210 + N211 --> N208 + N211 --> N204 + N212 --> N213 + N212 --> N214 + N215 --> N213 + N216 --> N213 + N217 --> N213 + N218 --> N213 + N219 --> N220 + N219 --> N213 + N221 --> N220 N221 --> N212 - N221 --> N205 - N221 --> N230 - N223 --> N212 - N223 --> N205 - N224 --> N212 - N224 --> N205 - N225 --> N212 - N225 --> N205 - N226 --> N212 - N226 --> N205 - N226 --> N231 - N226 --> N232 - N227 --> N212 - N227 --> N205 - N228 --> N212 - N228 --> N205 - N229 --> N212 - N229 --> N205 - N222 --> N212 - N222 --> N205 - N233 --> N212 - N233 --> N224 - N234 --> N205 - N230 --> N205 - N206 --> N205 - N235 --> N205 - N236 --> N206 - N237 --> N238 - N239 --> N205 - N240 --> N241 - N240 --> N238 - N242 --> N243 - N244 --> N245 - N244 --> N241 - N246 --> N241 - N246 --> N247 - N248 --> N241 - N249 --> N241 - N250 --> N241 - N251 --> N241 - N251 --> N238 - N251 --> N252 - N253 --> N254 - N255 --> N205 - N255 --> N238 - N256 --> N205 + N222 --> N220 + N222 --> N213 + N222 --> N215 + N223 --> N220 + N223 --> N213 + N224 --> N220 + N224 --> N218 + N225 --> N220 + N225 --> N213 + N226 --> N220 + N226 --> N213 + N226 --> N214 + N227 --> N220 + N227 --> N213 + N228 --> N219 + N228 --> N221 + N228 --> N222 + N228 --> N223 + N228 --> N224 + N228 --> N225 + N228 --> N226 + N228 --> N227 + N228 --> N229 + N228 --> N230 + N228 --> N231 + N228 --> N232 + N228 --> N233 + N228 --> N234 + N228 --> N235 + N228 --> N236 + N228 --> N237 + N228 --> N238 + N229 --> N220 + N229 --> N213 + N229 --> N239 + N231 --> N220 + N231 --> N213 + N232 --> N220 + N232 --> N213 + N233 --> N220 + N233 --> N213 + N235 --> N220 + N235 --> N213 + N235 --> N240 + N235 --> N241 + N236 --> N220 + N236 --> N213 + N237 --> N220 + N237 --> N213 + N238 --> N220 + N238 --> N213 + N230 --> N220 + N230 --> N213 + N242 --> N220 + N242 --> N232 + N234 --> N220 + N243 --> N220 + N243 --> N234 + N244 --> N213 + N239 --> N213 + N214 --> N213 + N245 --> N213 + N246 --> N214 + N247 --> N248 + N249 --> N213 + N250 --> N251 + N250 --> N248 + N252 --> N253 + N254 --> N255 + N254 --> N251 + N256 --> N251 N256 --> N257 - N258 --> N205 - N232 --> N205 - N232 --> N259 - N232 --> N260 - N260 --> N205 + N258 --> N251 + N259 --> N251 + N260 --> N251 + N261 --> N251 + N261 --> N248 N261 --> N262 - N263 --> N205 - N264 --> N265 - N264 --> N266 - N266 --> N205 - N267 --> N205 - N231 --> N205 - N259 --> N205 - N268 --> N269 - N270 --> N205 - N270 --> N257 - N271 --> N265 - N272 --> N195 - N272 --> N180 - N273 --> N274 + N263 --> N264 + N265 --> N213 + N265 --> N248 + N266 --> N213 + N266 --> N265 + N267 --> N213 + N267 --> N268 + N269 --> N213 + N241 --> N213 + N241 --> N270 + N241 --> N271 + N271 --> N213 + N272 --> N273 + N274 --> N213 N275 --> N276 - N277 --> N180 - N278 --> N180 + N275 --> N277 + N277 --> N213 + N278 --> N213 + N240 --> N213 + N270 --> N213 N279 --> N280 - N279 --> N281 - N279 --> N275 - N282 --> N283 - N282 --> N284 - N281 --> N285 - N281 --> N286 - N281 --> N282 + N281 --> N213 + N281 --> N268 + N282 --> N276 + N283 --> N201 + N283 --> N186 + N284 --> N285 + N286 --> N287 + N288 --> N186 + N289 --> N186 + N290 --> N291 + N290 --> N292 + N290 --> N286 + N293 --> N202 + N294 --> N295 + N294 --> N296 + N292 --> N297 + N292 --> N298 + N292 --> N294 ``` diff --git a/docs/architecture/indexion-dependency-graph-02-server-tests.md b/docs/architecture/indexion-dependency-graph-02-server-tests.md index 7f488a1f9..51a6b3d90 100644 --- a/docs/architecture/indexion-dependency-graph-02-server-tests.md +++ b/docs/architecture/indexion-dependency-graph-02-server-tests.md @@ -126,46 +126,51 @@ graph LR N121[npm:@solid-imager/core/domain/interfaces/ai-client] N122[unit/application/services/job-dispatch-service.test.ts] N123[unit/application/services/job-transfer-storage.test.ts] - N124[unit/config/database.test.ts] - N125[unit/db/connection.test.ts] - N126[unit/domain/media/schemas.test.ts] - N127[unit/domain/media/utils/hash-utils.test.ts] - N128[apps/server/node_modules/@solid-imager/application/src/index.ts] - N129[unit/domain/media/utils/metadata-utils.test.ts] - N130[npm:@solid-imager/core/domain/media/utils/metadata-utils] - N131[unit/domain/search-mode-transition.test.ts] - N132[npm:@solid-imager/core/domain/search/logic] - N133[unit/infrastructure/api-clients/ai-api.test.ts] - N134[npm:~/infrastructure/api-clients/ai-api] - N135[unit/infrastructure/api-clients/downloads-api.test.ts] - N136[npm:~/infrastructure/api-clients/downloads-api] - N137[unit/infrastructure/api-clients/sources-api-ext.test.ts] - N138[unit/infrastructure/file-system/node-file-system.test.ts] - N139[npm:~/infrastructure/file-system/node-file-system] - N140[unit/infrastructure/jobs/download-jobs.test.ts] - N141[npm:~/infrastructure/jobs/download-jobs] - N142[unit/infrastructure/jobs/download-rate-limiter.test.ts] - N143[unit/infrastructure/jobs/job-worker.test.ts] - N144[npm:@solid-imager/core/domain/config/config-schema] - N145[npm:~/domain/repositories/job-repository] - N146[npm:~/infrastructure/jobs/job-worker] - N147[unit/infrastructure/jobs/ccip-jobs.test.ts] - N148[unit/infrastructure/jobs/tagging-jobs.test.ts] - N149[unit/infrastructure/storage/server-media-storage.test.ts] - N150[apps/server/node_modules/fluent-ffmpeg/index.js] - N151[unit/infrastructure/events/realtime-event-bus.test.ts] - N152[npm:@solid-imager/core/domain/sources/events] - N153[npm:~/infrastructure/events/realtime-event-bus] - N154[unit/infrastructure/api/rpc-response-headers.test.ts] - N155[apps/server/node_modules/@orpc/server/dist/index.d.mts] - N156[npm:@orpc/server/fetch] - N157[npm:@orpc/server/plugins] - N158[npm:~/infrastructure/api/rpc-response-headers] - N159[unit/media/copy-media-job.test.ts] - N160[npm:~/infrastructure/jobs/thumbnails] - N161[unit/security/file-validation.test.ts] - N162[unit/server-config-service.test.ts] - N163[npm:~/infrastructure/services/server-config-service] + N124[unit/application/services/search-snapshot-service.test.ts] + N125[npm:@solid-imager/core/domain/errors] + N126[npm:@solid-imager/core/domain/repositories/search-snapshot-repository] + N127[unit/config/database.test.ts] + N128[unit/db/connection.test.ts] + N129[unit/domain/media/schemas.test.ts] + N130[unit/domain/media/utils/hash-utils.test.ts] + N131[apps/server/node_modules/@solid-imager/application/src/index.ts] + N132[unit/domain/media/utils/metadata-utils.test.ts] + N133[npm:@solid-imager/core/domain/media/utils/metadata-utils] + N134[unit/domain/search-mode-transition.test.ts] + N135[npm:@solid-imager/core/domain/search/logic] + N136[unit/infrastructure/api-clients/ai-api.test.ts] + N137[npm:~/infrastructure/api-clients/ai-api] + N138[unit/infrastructure/api-clients/downloads-api.test.ts] + N139[npm:~/infrastructure/api-clients/downloads-api] + N140[unit/infrastructure/api-clients/sources-api-ext.test.ts] + N141[unit/infrastructure/file-system/node-file-system.test.ts] + N142[npm:~/infrastructure/file-system/node-file-system] + N143[unit/infrastructure/jobs/download-jobs.test.ts] + N144[npm:~/infrastructure/jobs/download-jobs] + N145[unit/infrastructure/jobs/download-rate-limiter.test.ts] + N146[unit/infrastructure/jobs/job-worker.test.ts] + N147[npm:@solid-imager/core/domain/config/config-schema] + N148[npm:~/domain/repositories/job-repository] + N149[npm:~/infrastructure/jobs/job-worker] + N150[unit/infrastructure/jobs/ccip-jobs.test.ts] + N151[unit/infrastructure/jobs/tagging-jobs.test.ts] + N152[unit/infrastructure/storage/server-media-storage.test.ts] + N153[apps/server/node_modules/fluent-ffmpeg/index.js] + N154[unit/infrastructure/events/realtime-event-bus.test.ts] + N155[npm:@solid-imager/core/domain/sources/events] + N156[npm:~/infrastructure/events/realtime-event-bus] + N157[unit/infrastructure/api/rpc-response-headers.test.ts] + N158[apps/server/node_modules/@orpc/server/dist/index.d.mts] + N159[npm:@orpc/server/fetch] + N160[npm:@orpc/server/plugins] + N161[npm:~/infrastructure/api/rpc-response-headers] + N162[unit/infrastructure/ai/inference-options.test.ts] + N163[npm:~/infrastructure/ai/inference-options] + N164[unit/media/copy-media-job.test.ts] + N165[npm:~/infrastructure/jobs/thumbnails] + N166[unit/security/file-validation.test.ts] + N167[unit/server-config-service.test.ts] + N168[npm:~/infrastructure/services/server-config-service] N0 --> N1 N0 --> N2 N3 --> N1 @@ -428,71 +433,75 @@ graph LR N123 --> N23 N123 --> N107 N123 --> N1 - N124 --> N53 - N124 --> N23 - N124 --> N1 - N124 --> N57 - N125 --> N1 - N125 --> N57 - N126 --> N7 - N126 --> N1 - N127 --> N21 + N124 --> N125 + N124 --> N126 N127 --> N53 - N127 --> N54 N127 --> N23 - N127 --> N128 N127 --> N1 - N129 --> N130 + N127 --> N57 + N128 --> N1 + N128 --> N57 + N129 --> N7 N129 --> N1 - N131 --> N7 - N131 --> N132 - N133 --> N1 - N133 --> N134 - N135 --> N1 - N135 --> N136 - N137 --> N1 - N138 --> N22 - N138 --> N54 - N138 --> N23 + N130 --> N21 + N130 --> N53 + N130 --> N54 + N130 --> N23 + N130 --> N131 + N130 --> N1 + N132 --> N133 + N132 --> N1 + N134 --> N7 + N134 --> N135 + N136 --> N1 + N136 --> N137 N138 --> N1 N138 --> N139 N140 --> N1 - N140 --> N141 - N140 --> N64 - N142 --> N1 - N143 --> N144 + N141 --> N22 + N141 --> N54 + N141 --> N23 + N141 --> N1 + N141 --> N142 N143 --> N1 - N143 --> N145 - N143 --> N2 - N143 --> N146 - N147 --> N1 - N147 --> N145 - N148 --> N1 - N148 --> N145 - N149 --> N22 - N149 --> N150 - N149 --> N43 - N149 --> N1 - N149 --> N70 - N151 --> N152 + N143 --> N144 + N143 --> N64 + N145 --> N1 + N146 --> N147 + N146 --> N1 + N146 --> N148 + N146 --> N2 + N146 --> N149 + N150 --> N1 + N150 --> N148 N151 --> N1 - N151 --> N153 + N151 --> N148 + N152 --> N22 + N152 --> N153 + N152 --> N43 + N152 --> N1 + N152 --> N70 N154 --> N155 - N154 --> N156 - N154 --> N157 N154 --> N1 - N154 --> N8 - N154 --> N158 - N159 --> N1 - N159 --> N160 - N159 --> N64 - N159 --> N68 - N159 --> N69 - N161 --> N1 - N161 --> N69 - N162 --> N53 - N162 --> N22 - N162 --> N144 + N154 --> N156 + N157 --> N158 + N157 --> N159 + N157 --> N160 + N157 --> N1 + N157 --> N8 + N157 --> N161 N162 --> N1 N162 --> N163 + N164 --> N1 + N164 --> N165 + N164 --> N64 + N164 --> N68 + N164 --> N69 + N166 --> N1 + N166 --> N69 + N167 --> N53 + N167 --> N22 + N167 --> N147 + N167 --> N1 + N167 --> N168 ``` diff --git a/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md b/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md index aa2dfa091..73bd2bc06 100644 --- a/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md +++ b/docs/architecture/indexion-dependency-graph-03-server-infrastructure-routes.md @@ -6,814 +6,887 @@ graph LR N1[npm:node:os] N2[npm:node:path] N3[apps/server/node_modules/@solid-imager/client/src/index.ts] - N4[npm:@solid-imager/core/domain/contract] - N5[npm:@solid-imager/core/domain/interfaces/ai-client] - N6[infrastructure/api-clients/ai-api.ts] - N7[npm:@solid-imager/core/domain/tagging/schemas] - N8[apps/server/node_modules/zod/index.d.cts] - N9[npm:~/infrastructure/api-clients/orpc-client] - N10[infrastructure/api-clients/characters-api.ts] - N11[infrastructure/api-clients/downloads-api.ts] - N12[npm:@solid-imager/core/domain/media/schemas] - N13[infrastructure/api-clients/fetch-url-api.ts] - N14[infrastructure/api-clients/ips-api.ts] - N15[infrastructure/api-clients/media-api.ts] - N16[infrastructure/api-clients/search-api.ts] - N17[infrastructure/api-clients/orpc-client.ts] - N18[apps/server/node_modules/@orpc/server/dist/index.d.mts] - N19[npm:@tanstack/solid-start] - N20[npm:@tanstack/solid-start/server] - N21[npm:~/infrastructure/api/app-router] - N22[infrastructure/api-clients/projects-api.ts] - N23[infrastructure/api-clients/queries/index.ts] - N24[apps/server/node_modules/@orpc/solid-query/dist/index.d.mts] - N25[infrastructure/api-clients/sources-api.ts] - N26[npm:@solid-imager/core/domain/sources/schemas] - N27[infrastructure/api-clients/thumbnails.ts] - N28[infrastructure/api/clients/preset-client.ts] - N29[npm:@solid-imager/core/domain/contract/presets-client] - N30[infrastructure/api/routers/ai-router.ts] - N31[infrastructure/api/routers/authors-router.ts] - N32[npm:@solid-imager/core/domain/contract/authors.contract] - N33[npm:~/infrastructure/repositories/authors-repository] - N34[infrastructure/api/routers/categories-router.ts] - N35[npm:@solid-imager/core/domain/contract/categories.contract] - N36[npm:~/infrastructure/services/category-service] - N37[infrastructure/api/routers/characters-router.ts] - N38[npm:@solid-imager/core/domain/contract/characters.contract] - N39[npm:~/infrastructure/services/character-service] - N40[infrastructure/api/routers/entity-media-counts.ts] - N41[infrastructure/api/routers/config-router.ts] - N42[npm:@solid-imager/core/domain/contract/config.contract] - N43[npm:~/infrastructure/service-registry] - N44[infrastructure/api/routers/directories-router.ts] - N45[npm:@solid-imager/core/domain/contract/directories.contract] - N46[npm:~/infrastructure/services/directory-service] - N47[infrastructure/api/routers/downloads-router.ts] - N48[npm:@solid-imager/core/domain/contract/downloads.contract] - N49[npm:~/infrastructure/jobs/download-jobs] - N50[infrastructure/api/routers/imports-router.ts] - N51[npm:@solid-imager/core/domain/contract/imports.contract] - N52[npm:@solid-imager/core/domain/sources/events] - N53[apps/server/node_modules/drizzle-orm/index.d.ts] - N54[npm:~/infrastructure/db] - N55[npm:~/infrastructure/db/schema] - N56[npm:~/infrastructure/events/realtime-event-bus] - N57[npm:~/infrastructure/services/backup-service] - N58[infrastructure/api/routers/ips-router.ts] - N59[npm:@solid-imager/core/domain/contract/ips.contract] - N60[npm:~/infrastructure/services/ip-service] - N61[infrastructure/api/routers/media-router.ts] - N62[npm:@solid-imager/core/domain/contract/media.contract] - N63[npm:@solid-imager/core/domain/errors] - N64[npm:@solid-imager/core/utils/async-pool] - N65[npm:~/infrastructure/logger] - N66[npm:~/infrastructure/services/bulk-operation-service] - N67[npm:~/infrastructure/services/ccip-vector-service] - N68[npm:~/infrastructure/services/media-service] - N69[infrastructure/api/routers/presets-router.ts] - N70[npm:@solid-imager/core/domain/contract/presets.contract] - N71[npm:~/infrastructure/services/preset-service] - N72[infrastructure/api/routers/projects-router.ts] - N73[npm:@solid-imager/core/domain/contract/projects.contract] - N74[npm:~/infrastructure/services/project-service] - N75[infrastructure/api/routers/sources-router.ts] - N76[infrastructure/api/routers/tags-router.ts] - N77[npm:@solid-imager/core/domain/contract/tags.contract] - N78[npm:~/infrastructure/services/tag-service] - N79[infrastructure/api/routers/thumbnails-router.ts] - N80[npm:@solid-imager/core/domain/contract/thumbnails.contract] - N81[npm:~/infrastructure/services/thumbnail-service] - N82[infrastructure/api/routers/utils-router.ts] - N83[npm:@solid-imager/core/domain/contract/utils.contract] - N84[infrastructure/api/routers/jobs-router.ts] - N85[npm:@solid-imager/core/domain/contract/jobs.contract] - N86[infrastructure/api/job-artifact.ts] - N87[npm:node:fs] - N88[npm:node:fs/promises] - N89[npm:@solid-imager/core/domain/repositories/job-repository] - N90[npm:~/infrastructure/repositories/job-repository] - N91[npm:~/infrastructure/services/job-transfer-storage] - N92[npm:~/infrastructure/utils/stream-utils] - N93[infrastructure/api/app-router.ts] - N94[npm:~/infrastructure/api/routers/ai-router] - N95[npm:~/infrastructure/api/routers/authors-router] - N96[npm:~/infrastructure/api/routers/categories-router] - N97[npm:~/infrastructure/api/routers/characters-router] - N98[npm:~/infrastructure/api/routers/config-router] - N99[npm:~/infrastructure/api/routers/directories-router] - N100[npm:~/infrastructure/api/routers/downloads-router] - N101[npm:~/infrastructure/api/routers/imports-router] - N102[npm:~/infrastructure/api/routers/ips-router] - N103[npm:~/infrastructure/api/routers/jobs-router] - N104[npm:~/infrastructure/api/routers/media-router] - N105[npm:~/infrastructure/api/routers/presets-router] - N106[npm:~/infrastructure/api/routers/projects-router] - N107[npm:~/infrastructure/api/routers/sources-router] - N108[npm:~/infrastructure/api/routers/tags-router] - N109[npm:~/infrastructure/api/routers/thumbnails-router] - N110[npm:~/infrastructure/api/routers/utils-router] - N111[infrastructure/bootstrap.ts] - N112[npm:~/infrastructure/ai/rust-ai-client] - N113[npm:~/infrastructure/db/transaction-manager] - N114[npm:~/infrastructure/file-system/node-file-system] - N115[npm:~/infrastructure/jobs/download-rate-limiter] - N116[npm:~/infrastructure/jobs/job-worker] - N117[infrastructure/db/__mocks__/index.ts] - N118[npm:uuid] - N119[apps/server/node_modules/vitest/dist/index.js] - N120[infrastructure/db/connection.ts] - N121[apps/server/node_modules/@electric-sql/pglite/dist/index.cjs] - N122[apps/server/node_modules/pg/esm/index.mjs] - N123[npm:~/config/database] - N124[infrastructure/db/pglite.ts] - N125[infrastructure/db/data-migration.ts] - N126[npm:~/infrastructure/db/index] - N127[infrastructure/db/executor.ts] - N128[npm:@solid-imager/db/types] - N129[infrastructure/db/index.ts] - N130[apps/server/node_modules/drizzle-orm/node-postgres/index.d.ts] - N131[apps/server/node_modules/drizzle-orm/pglite/index.d.ts] - N132[infrastructure/db/schema.ts] - N133[apps/server/node_modules/@electric-sql/pglite-pgvector/dist/index.cjs] - N134[infrastructure/file-system/node-file-system.ts] - N135[apps/server/node_modules/@solid-imager/core/src/index.ts] - N136[infrastructure/jobs/download-jobs.ts] - N137[infrastructure/jobs/download-rate-limiter.ts] - N138[npm:@solid-imager/core/domain/config/config-schema] - N139[infrastructure/jobs/file-watcher-service.ts] - N140[npm:~/infrastructure/jobs/file-watcher-manager] - N141[npm:~/infrastructure/jobs/thumbnails] - N142[npm:~/infrastructure/repositories/media-repository] - N143[npm:~/infrastructure/repositories/source-repository] - N144[npm:~/infrastructure/services/directory-sync-service] - N145[npm:~/infrastructure/services/media-processing-service] - N146[npm:~/infrastructure/storage/server-media-storage] - N147[infrastructure/jobs/job-worker.ts] - N148[npm:~/domain/repositories/job-repository] - N149[infrastructure/jobs/tagging-jobs.ts] - N150[infrastructure/jobs/tag-extraction.ts] - N151[npm:~/infrastructure/processing/image-processor] - N152[npm:~/infrastructure/repositories/tag-repository] - N153[infrastructure/jobs/ccip-jobs.ts] - N154[npm:@solid-imager/application/ports/ccip-vector-store] - N155[infrastructure/jobs/thumbnails.ts] - N156[infrastructure/jobs/file-watcher-manager.ts] - N157[apps/server/node_modules/chokidar/index.js] - N158[infrastructure/logger.ts] - N159[apps/server/node_modules/pino/pino.js] - N160[infrastructure/processing/image-processor.ts] - N161[apps/server/node_modules/sharp/dist/index.cjs] - N162[infrastructure/repositories/author-repository.ts] - N163[npm:@solid-imager/core/domain/repositories/author-repository] - N164[npm:@solid-imager/db/repositories/author-repository] - N165[npm:~/infrastructure/db/executor] - N166[infrastructure/repositories/authors-repository.ts] - N167[npm:@solid-imager/core/domain/repositories/authors-repository] - N168[npm:@solid-imager/db/repositories/authors-repository] - N169[infrastructure/repositories/category-repository.ts] - N170[npm:@solid-imager/core/domain/repositories/category-repository] - N171[npm:@solid-imager/db/repositories/category-repository] - N172[infrastructure/repositories/character-repository.ts] - N173[npm:@solid-imager/core/domain/repositories/character-repository] - N174[npm:@solid-imager/db/repositories/character-repository] - N175[infrastructure/repositories/collection-repository.ts] - N176[npm:@solid-imager/core/domain/repositories/collection-repository] - N177[npm:@solid-imager/db/repositories/collection-repository] - N178[infrastructure/repositories/ip-repository.ts] - N179[npm:@solid-imager/core/domain/repositories/ip-repository] - N180[npm:@solid-imager/db/repositories/ip-repository] - N181[infrastructure/repositories/job-repository.ts] - N182[npm:@solid-imager/db/repositories/job-repository] - N183[infrastructure/repositories/media-repository-utils.ts] - N184[npm:@solid-imager/db/repositories/media-repository-utils] - N185[infrastructure/repositories/media-repository.ts] - N186[npm:@solid-imager/core/domain/repositories/media-repository] - N187[npm:@solid-imager/db/repositories/media-repository] - N188[npm:~/infrastructure/repositories/author-repository] - N189[infrastructure/repositories/preset-repository.ts] - N190[npm:@solid-imager/core/domain/repositories/preset-repository] - N191[npm:@solid-imager/db/repositories/preset-repository] - N192[infrastructure/repositories/project-repository.ts] - N193[npm:@solid-imager/core/domain/repositories/project-repository] - N194[npm:@solid-imager/db/repositories/project-repository] - N195[infrastructure/repositories/source-repository.ts] - N196[npm:@solid-imager/core/domain/repositories/source-repository] - N197[npm:@solid-imager/db/repositories/source-repository] - N198[infrastructure/repositories/tag-repository.ts] - N199[npm:@solid-imager/core/domain/repositories/tag-repository] - N200[npm:@solid-imager/db/repositories/tag-repository] - N201[infrastructure/repositories/user-repository.ts] - N202[npm:@solid-imager/core/domain/repositories/user-repository] - N203[npm:@solid-imager/db/repositories/user-repository] - N204[infrastructure/storage/factory.ts] - N205[infrastructure/storage/local.ts] - N206[infrastructure/storage/schema.ts] - N207[npm:node:stream] - N208[infrastructure/storage/server-media-storage.ts] - N209[infrastructure/utils/ffmpeg.ts] - N210[apps/server/node_modules/fluent-ffmpeg/index.js] - N211[infrastructure/utils/stream-utils.ts] - N212[infrastructure/events/realtime-event-bus.ts] - N213[npm:node:events] - N214[infrastructure/router/route-types.ts] - N215[apps/server/node_modules/@tanstack/solid-query/build/index.cjs] - N216[infrastructure/server-route-bootstrap.ts] - N217[infrastructure/services/author-service.ts] - N218[npm:@solid-imager/application/services/author-service] - N219[infrastructure/services/backup-service.ts] - N220[npm:node:stream/promises] - N221[infrastructure/services/bulk-operation-service.ts] - N222[infrastructure/services/category-service.ts] - N223[npm:@solid-imager/application/services/category-service] - N224[npm:~/infrastructure/repositories/category-repository] - N225[infrastructure/services/ccip-vector-service.ts] - N226[npm:@solid-imager/application/ports/media-service] - N227[npm:@solid-imager/application/services/ccip-vector-service] - N228[npm:~/infrastructure/ai/postgres-ccip-vector-store] - N229[npm:~/infrastructure/services/tagging-service] - N230[infrastructure/services/collection-service.ts] - N231[npm:@solid-imager/application/services/collection-service] - N232[npm:~/infrastructure/repositories/collection-repository] - N233[infrastructure/services/directory-service.ts] - N234[npm:~/infrastructure/services/media-source-service] - N235[npm:~/infrastructure/storage/factory] - N236[infrastructure/services/directory-sync-service.ts] - N237[infrastructure/services/ip-service.ts] - N238[npm:@solid-imager/application/services/ip-service] - N239[npm:~/infrastructure/repositories/ip-repository] - N240[infrastructure/services/job-dispatch-service.ts] - N241[infrastructure/services/job-transfer-storage.ts] - N242[infrastructure/services/maintenance-service.ts] - N243[infrastructure/services/media-processing-service.ts] - N244[npm:@solid-imager/core/domain/interfaces/transaction-manager] - N245[infrastructure/services/preset-service.ts] - N246[apps/server/node_modules/@solid-imager/application/src/index.ts] - N247[npm:@solid-imager/application/services/preset-service] - N248[npm:~/infrastructure/repositories/preset-repository] - N249[infrastructure/services/project-service.ts] - N250[npm:@solid-imager/application/services/project-service] - N251[npm:~/infrastructure/repositories/project-repository] - N252[infrastructure/services/search-service.ts] - N253[npm:@solid-imager/application/services/search-service] - N254[infrastructure/services/server-config-service.ts] - N255[npm:node:util] - N256[infrastructure/services/source-transfer-job-service.ts] - N257[infrastructure/services/tag-service.ts] - N258[npm:@solid-imager/application/services/tag-service] - N259[infrastructure/services/tagging-service.ts] - N260[npm:@solid-imager/application/services/tagging-service] - N261[infrastructure/services/thumbnail-service.ts] - N262[npm:@solid-imager/core/domain/thumbnails/schemas] - N263[infrastructure/services/user-service.ts] - N264[npm:@solid-imager/application/services/user-service] - N265[npm:~/infrastructure/repositories/user-repository] - N266[routes/$.tsx] - N267[npm:@solid-imager/ui/screens/not-found-screen] - N268[apps/server/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] - N269[routes/__root.tsx] - N270[npm:@solid-imager/ui/layouts/app-shell] - N271[npm:@solid-imager/ui/router-status] - N272[npm:@solid-imager/ui/toast] - N273[routes/about.tsx] - N274[npm:@solid-imager/ui/counter] - N275[routes/api/rpc.$.ts] - N276[npm:@orpc/server/fetch] - N277[npm:@orpc/server/plugins] - N278[npm:~/infrastructure/api/rpc-response-headers] - N279[npm:~/infrastructure/router/route-types] - N280[npm:~/infrastructure/server-route-bootstrap] - N281[routes/api/sources.$mediaSourceId.$mediaId.ts] - N282[npm:@solid-imager/core/domain/media/utils/media-type-utils] - N283[routes/api/jobs.$jobId.artifact.ts] - N284[routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts] - N285[routes/config.tsx] - N286[npm:@solid-imager/ui/query-options] - N287[npm:@solid-imager/ui/query-state] - N288[npm:@solid-imager/ui/screens/legacy-config-state-screen] - N289[npm:~/infrastructure/api-clients/queries] - N290[routes/v2/$.tsx] - N291[npm:@solid-imager/ui/button] - N292[npm:@solid-imager/ui/v2/icons] - N293[routes/v2/about.tsx] - N294[npm:@solid-imager/ui/badge] - N295[routes/v2/config.tsx] - N296[npm:@solid-imager/ui/screens/v2-config-state-screen] - N297[routes/v2/index.tsx] - N298[routes/v2/jobs.tsx] - N299[npm:@solid-imager/core/domain/jobs/schemas] - N300[npm:@solid-imager/ui/hooks/use-job-events] - N301[npm:@solid-imager/ui/screens/v2-jobs-screen] - N302[routes/v2/manager.tsx] - N303[npm:@solid-imager/ui/hooks/use-manager-page] - N304[npm:@solid-imager/ui/screens/v2-manager/types] - N305[npm:@solid-imager/ui/screens/v2-manager-screen] - N306[npm:~/hooks/use-batch-job-events] - N307[routes/v2/media-context.ts] - N308[routes/v2/route.tsx] - N309[apps/server/node_modules/solid-js/types/index.d.ts] - N310[npm:~/components/api-activity-indicator] - N311[npm:~/components/v2/v2-app-shell] - N312[routes/v2/search.tsx] - N313[routes/v2/sources/$mediaSourceId/$mediaId/index.tsx] - N314[npm:@solid-imager/ui/screens/v2-media-detail-screen] - N315[routes/v2/sources/$mediaSourceId/index.tsx] - N316[npm:~/routes/sources/$mediaSourceId/components/v2-source-media-page] - N317[routes/docs/swagger/index.tsx] - N318[routes/index.tsx] - N319[routes/manager.tsx] - N320[npm:@solid-imager/ui/screens/manager-screen] - N321[routes/search.tsx] - N322[routes/sources/$mediaSourceId/$mediaId/index.tsx] - N323[npm:@solid-imager/ui/screens/legacy-media-detail-screen] - N324[routes/sources/$mediaSourceId/components/source-media-page.tsx] - N325[npm:@solid-imager/ui/hooks/use-current-search-persistence] - N326[npm:@solid-imager/ui/preset-client] - N327[npm:@solid-imager/ui/screens/source-media-screen.types] - N328[npm:@solid-imager/ui/source-media-page] - N329[routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx] - N330[npm:@solid-imager/ui/screens/source-media-screen] - N331[npm:~/components/media/legacy-media-grid-item] - N332[npm:~/components/upload-media-modal] - N333[routes/sources/$mediaSourceId/components/v2-source-media-page.tsx] - N334[npm:@solid-imager/ui/screens/v2-source-media-screen] - N335[npm:~/components/media/thumbnail-image] - N336[npm:~/components/media/v2-media-grid-item] - N337[npm:~/components/v2-upload-media-modal] - N338[npm:~/routes/v2/media-context] - N339[routes/sources/$mediaSourceId/index.tsx] - N340[routes/sources/index.tsx] - N341[npm:@solid-imager/ui/hooks/use-sources-events] - N342[npm:@solid-imager/ui/hooks/use-sources-page] - N343[npm:@solid-imager/ui/legacy-source-form-modal] - N344[npm:@solid-imager/ui/screens/sources-screen] - N345[npm:@solid-imager/ui/source-card] - N346[npm:@solid-imager/ui/source-delete-modal] - N347[npm:~/hooks/use-media-source-events] - N348[routes/design-lab.tsx] - N349[npm:@solid-imager/ui/screens/design-concept-screen] + N4[npm:@solid-imager/core/domain/config/config-schema] + N5[npm:@solid-imager/core/domain/contract] + N6[npm:@solid-imager/core/domain/interfaces/ai-client] + N7[infrastructure/ai/inference-options.ts] + N8[apps/server/node_modules/dghs-imgutils-rs/index.js] + N9[infrastructure/api-clients/ai-api.ts] + N10[npm:@solid-imager/core/domain/tagging/schemas] + N11[apps/server/node_modules/zod/index.d.cts] + N12[npm:~/infrastructure/api-clients/orpc-client] + N13[infrastructure/api-clients/orpc-client.ts] + N14[apps/server/node_modules/@orpc/server/dist/index.d.mts] + N15[npm:@tanstack/solid-start] + N16[npm:@tanstack/solid-start/server] + N17[npm:~/infrastructure/api/app-router] + N18[infrastructure/api-clients/characters-api.ts] + N19[infrastructure/api-clients/downloads-api.ts] + N20[npm:@solid-imager/core/domain/media/schemas] + N21[infrastructure/api-clients/fetch-url-api.ts] + N22[infrastructure/api-clients/ips-api.ts] + N23[infrastructure/api-clients/media-api.ts] + N24[infrastructure/api-clients/search-api.ts] + N25[infrastructure/api-clients/projects-api.ts] + N26[infrastructure/api-clients/queries/index.ts] + N27[apps/server/node_modules/@orpc/solid-query/dist/index.d.mts] + N28[infrastructure/api-clients/sources-api.ts] + N29[npm:@solid-imager/core/domain/sources/schemas] + N30[infrastructure/api-clients/thumbnails.ts] + N31[infrastructure/api/clients/preset-client.ts] + N32[npm:@solid-imager/core/domain/contract/presets-client] + N33[infrastructure/api/clients/search-history-client.ts] + N34[npm:@solid-imager/core/domain/contract/search-snapshots-client] + N35[infrastructure/api/routers/ai-router.ts] + N36[infrastructure/api/routers/authors-router.ts] + N37[npm:@solid-imager/core/domain/contract/authors.contract] + N38[npm:~/infrastructure/repositories/authors-repository] + N39[infrastructure/api/routers/categories-router.ts] + N40[npm:@solid-imager/core/domain/contract/categories.contract] + N41[npm:~/infrastructure/services/category-service] + N42[infrastructure/api/routers/characters-router.ts] + N43[npm:@solid-imager/core/domain/contract/characters.contract] + N44[npm:~/infrastructure/services/character-service] + N45[infrastructure/api/routers/entity-media-counts.ts] + N46[infrastructure/api/routers/config-router.ts] + N47[npm:@solid-imager/core/domain/contract/config.contract] + N48[npm:~/infrastructure/service-registry] + N49[infrastructure/api/routers/directories-router.ts] + N50[npm:@solid-imager/core/domain/contract/directories.contract] + N51[npm:~/infrastructure/services/directory-service] + N52[infrastructure/api/routers/downloads-router.ts] + N53[npm:@solid-imager/core/domain/contract/downloads.contract] + N54[npm:~/infrastructure/jobs/download-jobs] + N55[infrastructure/api/routers/imports-router.ts] + N56[npm:@solid-imager/core/domain/contract/imports.contract] + N57[npm:@solid-imager/core/domain/sources/events] + N58[apps/server/node_modules/drizzle-orm/index.d.ts] + N59[npm:~/infrastructure/db] + N60[npm:~/infrastructure/db/schema] + N61[npm:~/infrastructure/events/realtime-event-bus] + N62[npm:~/infrastructure/services/backup-service] + N63[infrastructure/api/routers/ips-router.ts] + N64[npm:@solid-imager/core/domain/contract/ips.contract] + N65[npm:~/infrastructure/services/ip-service] + N66[infrastructure/api/routers/media-router.ts] + N67[npm:@solid-imager/core/domain/contract/media.contract] + N68[npm:@solid-imager/core/domain/errors] + N69[npm:@solid-imager/core/utils/async-pool] + N70[npm:~/infrastructure/logger] + N71[npm:~/infrastructure/services/bulk-operation-service] + N72[npm:~/infrastructure/services/ccip-vector-service] + N73[npm:~/infrastructure/services/media-service] + N74[infrastructure/api/routers/presets-router.ts] + N75[npm:@solid-imager/core/domain/contract/presets.contract] + N76[npm:~/infrastructure/services/preset-service] + N77[infrastructure/api/routers/projects-router.ts] + N78[npm:@solid-imager/core/domain/contract/projects.contract] + N79[npm:~/infrastructure/services/project-service] + N80[infrastructure/api/routers/sources-router.ts] + N81[infrastructure/api/routers/tags-router.ts] + N82[npm:@solid-imager/core/domain/contract/tags.contract] + N83[npm:~/infrastructure/services/tag-service] + N84[infrastructure/api/routers/thumbnails-router.ts] + N85[npm:@solid-imager/core/domain/contract/thumbnails.contract] + N86[npm:~/infrastructure/services/thumbnail-service] + N87[infrastructure/api/routers/utils-router.ts] + N88[npm:@solid-imager/core/domain/contract/utils.contract] + N89[infrastructure/api/routers/jobs-router.ts] + N90[npm:@solid-imager/core/domain/contract/jobs.contract] + N91[infrastructure/api/routers/search-snapshots-router.ts] + N92[npm:@solid-imager/core/domain/contract/search-snapshots.contract] + N93[npm:~/infrastructure/services/search-snapshot-service] + N94[infrastructure/api/job-artifact.ts] + N95[npm:node:fs] + N96[npm:node:fs/promises] + N97[npm:@solid-imager/core/domain/repositories/job-repository] + N98[npm:~/infrastructure/repositories/job-repository] + N99[npm:~/infrastructure/services/job-transfer-storage] + N100[npm:~/infrastructure/utils/stream-utils] + N101[infrastructure/api/app-router.ts] + N102[npm:~/infrastructure/api/routers/ai-router] + N103[npm:~/infrastructure/api/routers/authors-router] + N104[npm:~/infrastructure/api/routers/categories-router] + N105[npm:~/infrastructure/api/routers/characters-router] + N106[npm:~/infrastructure/api/routers/config-router] + N107[npm:~/infrastructure/api/routers/directories-router] + N108[npm:~/infrastructure/api/routers/downloads-router] + N109[npm:~/infrastructure/api/routers/imports-router] + N110[npm:~/infrastructure/api/routers/ips-router] + N111[npm:~/infrastructure/api/routers/jobs-router] + N112[npm:~/infrastructure/api/routers/media-router] + N113[npm:~/infrastructure/api/routers/presets-router] + N114[npm:~/infrastructure/api/routers/projects-router] + N115[npm:~/infrastructure/api/routers/search-snapshots-router] + N116[npm:~/infrastructure/api/routers/sources-router] + N117[npm:~/infrastructure/api/routers/tags-router] + N118[npm:~/infrastructure/api/routers/thumbnails-router] + N119[npm:~/infrastructure/api/routers/utils-router] + N120[infrastructure/bootstrap.ts] + N121[npm:~/infrastructure/ai/rust-ai-client] + N122[npm:~/infrastructure/db/transaction-manager] + N123[npm:~/infrastructure/file-system/node-file-system] + N124[npm:~/infrastructure/jobs/download-rate-limiter] + N125[npm:~/infrastructure/jobs/job-worker] + N126[infrastructure/db/__mocks__/index.ts] + N127[npm:uuid] + N128[apps/server/node_modules/vitest/dist/index.js] + N129[infrastructure/db/connection.ts] + N130[apps/server/node_modules/@electric-sql/pglite/dist/index.cjs] + N131[apps/server/node_modules/pg/esm/index.mjs] + N132[npm:~/config/database] + N133[infrastructure/db/pglite.ts] + N134[infrastructure/db/data-migration.ts] + N135[npm:~/infrastructure/db/index] + N136[infrastructure/db/executor.ts] + N137[npm:@solid-imager/db/types] + N138[infrastructure/db/index.ts] + N139[apps/server/node_modules/drizzle-orm/node-postgres/index.d.ts] + N140[apps/server/node_modules/drizzle-orm/pglite/index.d.ts] + N141[infrastructure/db/schema.ts] + N142[apps/server/node_modules/@electric-sql/pglite-pgvector/dist/index.cjs] + N143[infrastructure/file-system/node-file-system.ts] + N144[apps/server/node_modules/@solid-imager/core/src/index.ts] + N145[infrastructure/jobs/download-jobs.ts] + N146[infrastructure/jobs/download-rate-limiter.ts] + N147[infrastructure/jobs/file-watcher-service.ts] + N148[npm:~/infrastructure/jobs/file-watcher-manager] + N149[npm:~/infrastructure/jobs/thumbnails] + N150[npm:~/infrastructure/repositories/media-repository] + N151[npm:~/infrastructure/repositories/source-repository] + N152[npm:~/infrastructure/services/directory-sync-service] + N153[npm:~/infrastructure/services/media-processing-service] + N154[npm:~/infrastructure/storage/server-media-storage] + N155[infrastructure/jobs/ccip-jobs.ts] + N156[npm:@solid-imager/application/ports/ccip-vector-store] + N157[infrastructure/jobs/job-worker.ts] + N158[npm:~/domain/repositories/job-repository] + N159[infrastructure/jobs/tagging-jobs.ts] + N160[infrastructure/jobs/tag-extraction.ts] + N161[npm:~/infrastructure/processing/image-processor] + N162[npm:~/infrastructure/repositories/tag-repository] + N163[infrastructure/jobs/thumbnails.ts] + N164[infrastructure/jobs/file-watcher-manager.ts] + N165[apps/server/node_modules/chokidar/index.js] + N166[infrastructure/logger.ts] + N167[apps/server/node_modules/pino/pino.js] + N168[infrastructure/processing/image-processor.ts] + N169[apps/server/node_modules/sharp/dist/index.cjs] + N170[infrastructure/repositories/author-repository.ts] + N171[npm:@solid-imager/core/domain/repositories/author-repository] + N172[npm:@solid-imager/db/repositories/author-repository] + N173[npm:~/infrastructure/db/executor] + N174[infrastructure/repositories/authors-repository.ts] + N175[npm:@solid-imager/core/domain/repositories/authors-repository] + N176[npm:@solid-imager/db/repositories/authors-repository] + N177[infrastructure/repositories/category-repository.ts] + N178[npm:@solid-imager/core/domain/repositories/category-repository] + N179[npm:@solid-imager/db/repositories/category-repository] + N180[infrastructure/repositories/character-repository.ts] + N181[npm:@solid-imager/core/domain/repositories/character-repository] + N182[npm:@solid-imager/db/repositories/character-repository] + N183[infrastructure/repositories/collection-repository.ts] + N184[npm:@solid-imager/core/domain/repositories/collection-repository] + N185[npm:@solid-imager/db/repositories/collection-repository] + N186[infrastructure/repositories/ip-repository.ts] + N187[npm:@solid-imager/core/domain/repositories/ip-repository] + N188[npm:@solid-imager/db/repositories/ip-repository] + N189[infrastructure/repositories/job-repository.ts] + N190[npm:@solid-imager/db/repositories/job-repository] + N191[infrastructure/repositories/media-repository-utils.ts] + N192[npm:@solid-imager/db/repositories/media-repository-utils] + N193[infrastructure/repositories/media-repository.ts] + N194[npm:@solid-imager/core/domain/repositories/media-repository] + N195[npm:@solid-imager/db/repositories/media-repository] + N196[npm:~/infrastructure/repositories/author-repository] + N197[infrastructure/repositories/preset-repository.ts] + N198[npm:@solid-imager/core/domain/repositories/preset-repository] + N199[npm:@solid-imager/db/repositories/preset-repository] + N200[infrastructure/repositories/project-repository.ts] + N201[npm:@solid-imager/core/domain/repositories/project-repository] + N202[npm:@solid-imager/db/repositories/project-repository] + N203[infrastructure/repositories/source-repository.ts] + N204[npm:@solid-imager/core/domain/repositories/source-repository] + N205[npm:@solid-imager/db/repositories/source-repository] + N206[infrastructure/repositories/tag-repository.ts] + N207[npm:@solid-imager/core/domain/repositories/tag-repository] + N208[npm:@solid-imager/db/repositories/tag-repository] + N209[infrastructure/repositories/user-repository.ts] + N210[npm:@solid-imager/core/domain/repositories/user-repository] + N211[npm:@solid-imager/db/repositories/user-repository] + N212[infrastructure/repositories/search-snapshot-repository.ts] + N213[npm:@solid-imager/core/domain/repositories/search-snapshot-repository] + N214[npm:@solid-imager/db/repositories/search-snapshot-repository] + N215[infrastructure/storage/factory.ts] + N216[infrastructure/storage/local.ts] + N217[infrastructure/storage/schema.ts] + N218[npm:node:stream] + N219[infrastructure/storage/server-media-storage.ts] + N220[infrastructure/utils/ffmpeg.ts] + N221[apps/server/node_modules/fluent-ffmpeg/index.js] + N222[infrastructure/utils/stream-utils.ts] + N223[infrastructure/events/realtime-event-bus.ts] + N224[npm:node:events] + N225[infrastructure/router/route-types.ts] + N226[apps/server/node_modules/@tanstack/solid-query/build/index.cjs] + N227[infrastructure/server-route-bootstrap.ts] + N228[infrastructure/services/author-service.ts] + N229[npm:@solid-imager/application/services/author-service] + N230[infrastructure/services/backup-service.ts] + N231[npm:node:stream/promises] + N232[infrastructure/services/bulk-operation-service.ts] + N233[infrastructure/services/category-service.ts] + N234[npm:@solid-imager/application/services/category-service] + N235[npm:~/infrastructure/repositories/category-repository] + N236[infrastructure/services/ccip-vector-service.ts] + N237[npm:@solid-imager/application/ports/media-service] + N238[npm:@solid-imager/application/services/ccip-vector-service] + N239[npm:~/infrastructure/ai/postgres-ccip-vector-store] + N240[npm:~/infrastructure/services/tagging-service] + N241[infrastructure/services/collection-service.ts] + N242[npm:@solid-imager/application/services/collection-service] + N243[npm:~/infrastructure/repositories/collection-repository] + N244[infrastructure/services/directory-service.ts] + N245[npm:~/infrastructure/services/media-source-service] + N246[npm:~/infrastructure/storage/factory] + N247[infrastructure/services/directory-sync-service.ts] + N248[infrastructure/services/ip-service.ts] + N249[npm:@solid-imager/application/services/ip-service] + N250[npm:~/infrastructure/repositories/ip-repository] + N251[infrastructure/services/job-dispatch-service.ts] + N252[infrastructure/services/job-transfer-storage.ts] + N253[infrastructure/services/maintenance-service.ts] + N254[infrastructure/services/media-processing-service.ts] + N255[npm:@solid-imager/core/domain/interfaces/transaction-manager] + N256[infrastructure/services/preset-service.ts] + N257[apps/server/node_modules/@solid-imager/application/src/index.ts] + N258[npm:@solid-imager/application/services/preset-service] + N259[npm:~/infrastructure/repositories/preset-repository] + N260[infrastructure/services/project-service.ts] + N261[npm:@solid-imager/application/services/project-service] + N262[npm:~/infrastructure/repositories/project-repository] + N263[infrastructure/services/search-service.ts] + N264[npm:@solid-imager/application/services/search-service] + N265[infrastructure/services/server-config-service.ts] + N266[npm:node:util] + N267[infrastructure/services/source-transfer-job-service.ts] + N268[infrastructure/services/tag-service.ts] + N269[npm:@solid-imager/application/services/tag-service] + N270[infrastructure/services/tagging-service.ts] + N271[npm:@solid-imager/application/services/tagging-service] + N272[infrastructure/services/thumbnail-service.ts] + N273[npm:@solid-imager/core/domain/thumbnails/schemas] + N274[infrastructure/services/user-service.ts] + N275[npm:@solid-imager/application/services/user-service] + N276[npm:~/infrastructure/repositories/user-repository] + N277[infrastructure/services/search-snapshot-service.ts] + N278[npm:@solid-imager/application/services/search-snapshot-service] + N279[npm:~/infrastructure/repositories/search-snapshot-repository] + N280[routes/$.tsx] + N281[npm:@solid-imager/ui/screens/not-found-screen] + N282[apps/server/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] + N283[routes/__root.tsx] + N284[npm:@solid-imager/ui/layouts/app-shell] + N285[npm:@solid-imager/ui/router-status] + N286[npm:@solid-imager/ui/toast] + N287[routes/about.tsx] + N288[npm:@solid-imager/ui/counter] + N289[routes/api/rpc.$.ts] + N290[npm:@orpc/server/fetch] + N291[npm:@orpc/server/plugins] + N292[npm:~/infrastructure/api/rpc-response-headers] + N293[npm:~/infrastructure/router/route-types] + N294[npm:~/infrastructure/server-route-bootstrap] + N295[routes/api/sources.$mediaSourceId.$mediaId.ts] + N296[npm:@solid-imager/core/domain/media/utils/media-type-utils] + N297[routes/api/jobs.$jobId.artifact.ts] + N298[routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts] + N299[routes/config.tsx] + N300[npm:@solid-imager/ui/query-options] + N301[npm:@solid-imager/ui/query-state] + N302[npm:@solid-imager/ui/screens/legacy-config-state-screen] + N303[npm:~/infrastructure/api-clients/queries] + N304[routes/v2/$.tsx] + N305[npm:@solid-imager/ui/button] + N306[npm:@solid-imager/ui/v2/icons] + N307[routes/v2/about.tsx] + N308[npm:@solid-imager/ui/badge] + N309[routes/v2/config.tsx] + N310[npm:@solid-imager/ui/screens/v2-config-state-screen] + N311[routes/v2/index.tsx] + N312[routes/v2/jobs.tsx] + N313[npm:@solid-imager/core/domain/jobs/schemas] + N314[npm:@solid-imager/ui/hooks/use-job-events] + N315[npm:@solid-imager/ui/screens/v2-jobs-screen] + N316[routes/v2/manager.tsx] + N317[npm:@solid-imager/ui/hooks/use-manager-page] + N318[npm:@solid-imager/ui/screens/v2-manager/types] + N319[npm:@solid-imager/ui/screens/v2-manager-screen] + N320[npm:~/hooks/use-batch-job-events] + N321[routes/v2/media-context.ts] + N322[routes/v2/route.tsx] + N323[apps/server/node_modules/solid-js/types/index.d.ts] + N324[npm:~/components/api-activity-indicator] + N325[npm:~/components/v2/v2-app-shell] + N326[routes/v2/search.tsx] + N327[npm:@solid-imager/ui/search-history-route] + N328[routes/v2/sources/$mediaSourceId/$mediaId/index.tsx] + N329[npm:@solid-imager/ui/screens/v2-media-detail-screen] + N330[routes/v2/sources/$mediaSourceId/index.tsx] + N331[npm:~/routes/sources/$mediaSourceId/components/v2-source-media-page] + N332[routes/v2/components/v2-search-content.tsx] + N333[npm:@solid-imager/ui/hooks/use-current-search-persistence] + N334[npm:@solid-imager/ui/hooks/use-search-history-persistence] + N335[npm:@solid-imager/ui/hooks/use-search-page] + N336[npm:@solid-imager/ui/preset-client] + N337[npm:@solid-imager/ui/screens/v2-search-screen] + N338[npm:@solid-imager/ui/search-history-client] + N339[npm:~/components/media/thumbnail-image] + N340[npm:~/components/media/v2-media-grid-item] + N341[npm:~/hooks/use-media-source-events] + N342[npm:~/infrastructure/api/clients/preset-client] + N343[npm:~/infrastructure/api/clients/search-history-client] + N344[routes/docs/swagger/index.tsx] + N345[routes/index.tsx] + N346[routes/manager.tsx] + N347[npm:@solid-imager/ui/screens/manager-screen] + N348[routes/search.tsx] + N349[npm:@solid-imager/ui/screens/search-screen] + N350[npm:~/components/media/legacy-media-grid-item] + N351[routes/sources/$mediaSourceId/$mediaId/index.tsx] + N352[npm:@solid-imager/ui/screens/legacy-media-detail-screen] + N353[routes/sources/$mediaSourceId/components/source-media-page.tsx] + N354[npm:@solid-imager/ui/screens/source-media-screen.types] + N355[npm:@solid-imager/ui/source-media-page] + N356[routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx] + N357[npm:@solid-imager/ui/screens/source-media-screen] + N358[npm:~/components/upload-media-modal] + N359[routes/sources/$mediaSourceId/components/v2-source-media-page.tsx] + N360[npm:@solid-imager/ui/screens/v2-source-media-screen] + N361[npm:~/components/v2-upload-media-modal] + N362[npm:~/routes/v2/media-context] + N363[routes/sources/$mediaSourceId/index.tsx] + N364[routes/sources/index.tsx] + N365[npm:@solid-imager/ui/hooks/use-sources-events] + N366[npm:@solid-imager/ui/hooks/use-sources-page] + N367[npm:@solid-imager/ui/legacy-source-form-modal] + N368[npm:@solid-imager/ui/screens/sources-screen] + N369[npm:@solid-imager/ui/source-card] + N370[npm:@solid-imager/ui/source-delete-modal] + N371[routes/design-lab.tsx] + N372[npm:@solid-imager/ui/screens/design-concept-screen] N0 --> N1 N0 --> N2 N0 --> N3 N0 --> N4 N0 --> N5 - N6 --> N7 - N6 --> N8 - N6 --> N9 - N10 --> N9 - N11 --> N12 - N11 --> N9 - N13 --> N9 - N14 --> N9 - N15 --> N9 - N15 --> N16 - N17 --> N18 - N17 --> N3 - N17 --> N4 - N17 --> N19 - N17 --> N20 - N17 --> N21 - N22 --> N9 + N0 --> N6 + N7 --> N4 + N7 --> N8 + N9 --> N10 + N9 --> N11 + N9 --> N12 + N13 --> N14 + N13 --> N3 + N13 --> N5 + N13 --> N15 + N13 --> N16 + N13 --> N17 + N18 --> N12 + N19 --> N20 + N19 --> N12 + N21 --> N12 + N22 --> N12 + N23 --> N12 N23 --> N24 - N16 --> N12 - N16 --> N9 - N25 --> N3 - N25 --> N26 - N25 --> N8 - N25 --> N9 - N27 --> N9 + N25 --> N12 + N26 --> N27 + N24 --> N20 + N24 --> N12 + N28 --> N3 N28 --> N29 - N28 --> N9 - N30 --> N1 - N30 --> N2 - N30 --> N18 - N31 --> N18 + N28 --> N11 + N28 --> N12 + N30 --> N12 N31 --> N32 - N31 --> N33 - N34 --> N18 - N34 --> N35 - N34 --> N36 - N37 --> N18 - N37 --> N38 - N37 --> N39 - N37 --> N40 - N41 --> N18 - N41 --> N42 - N41 --> N43 - N44 --> N18 - N44 --> N45 - N44 --> N46 - N47 --> N18 - N47 --> N48 - N47 --> N49 - N50 --> N18 - N50 --> N51 - N50 --> N12 - N50 --> N52 - N50 --> N53 - N50 --> N8 - N50 --> N54 - N50 --> N55 - N50 --> N56 - N50 --> N49 - N50 --> N57 - N58 --> N18 - N58 --> N59 - N58 --> N60 - N58 --> N40 - N61 --> N18 - N61 --> N62 - N61 --> N63 - N61 --> N64 - N61 --> N65 - N61 --> N66 - N61 --> N67 - N61 --> N68 - N69 --> N18 - N69 --> N70 - N69 --> N71 - N72 --> N18 - N72 --> N73 - N72 --> N74 - N72 --> N40 - N75 --> N18 - N76 --> N18 - N76 --> N77 - N76 --> N78 - N79 --> N18 - N79 --> N80 - N79 --> N81 - N82 --> N18 - N82 --> N83 - N84 --> N18 + N31 --> N12 + N33 --> N34 + N33 --> N12 + N35 --> N1 + N35 --> N2 + N35 --> N14 + N36 --> N14 + N36 --> N37 + N36 --> N38 + N39 --> N14 + N39 --> N40 + N39 --> N41 + N42 --> N14 + N42 --> N43 + N42 --> N44 + N42 --> N45 + N46 --> N14 + N46 --> N47 + N46 --> N48 + N49 --> N14 + N49 --> N50 + N49 --> N51 + N52 --> N14 + N52 --> N53 + N52 --> N54 + N55 --> N14 + N55 --> N56 + N55 --> N20 + N55 --> N57 + N55 --> N58 + N55 --> N11 + N55 --> N59 + N55 --> N60 + N55 --> N61 + N55 --> N54 + N55 --> N62 + N63 --> N14 + N63 --> N64 + N63 --> N65 + N63 --> N45 + N66 --> N14 + N66 --> N67 + N66 --> N68 + N66 --> N69 + N66 --> N70 + N66 --> N71 + N66 --> N72 + N66 --> N73 + N74 --> N14 + N74 --> N75 + N74 --> N76 + N77 --> N14 + N77 --> N78 + N77 --> N79 + N77 --> N45 + N80 --> N14 + N81 --> N14 + N81 --> N82 + N81 --> N83 + N84 --> N14 N84 --> N85 - N40 --> N53 - N40 --> N54 - N86 --> N87 - N86 --> N88 - N86 --> N89 - N86 --> N90 - N86 --> N91 - N86 --> N92 - N93 --> N18 - N93 --> N4 - N93 --> N94 - N93 --> N95 - N93 --> N96 - N93 --> N97 - N93 --> N98 - N93 --> N99 - N93 --> N100 - N93 --> N101 - N93 --> N102 - N93 --> N103 - N93 --> N104 - N93 --> N105 - N93 --> N106 - N93 --> N107 - N93 --> N108 - N93 --> N109 - N93 --> N110 - N111 --> N112 - N111 --> N113 - N111 --> N56 - N111 --> N114 - N111 --> N115 - N111 --> N116 - N117 --> N118 - N117 --> N119 - N117 --> N55 + N84 --> N86 + N87 --> N14 + N87 --> N88 + N89 --> N14 + N89 --> N90 + N45 --> N58 + N45 --> N59 + N91 --> N14 + N91 --> N92 + N91 --> N93 + N94 --> N95 + N94 --> N96 + N94 --> N97 + N94 --> N98 + N94 --> N99 + N94 --> N100 + N101 --> N14 + N101 --> N5 + N101 --> N102 + N101 --> N103 + N101 --> N104 + N101 --> N105 + N101 --> N106 + N101 --> N107 + N101 --> N108 + N101 --> N109 + N101 --> N110 + N101 --> N111 + N101 --> N112 + N101 --> N113 + N101 --> N114 + N101 --> N115 + N101 --> N116 + N101 --> N117 + N101 --> N118 + N101 --> N119 N120 --> N121 N120 --> N122 + N120 --> N61 N120 --> N123 N120 --> N124 - N125 --> N53 - N125 --> N126 - N127 --> N128 - N127 --> N126 - N129 --> N2 - N129 --> N121 + N120 --> N125 + N126 --> N127 + N126 --> N128 + N126 --> N60 N129 --> N130 N129 --> N131 - N129 --> N122 - N129 --> N65 - N129 --> N124 N129 --> N132 - N124 --> N121 - N124 --> N133 - N134 --> N88 + N129 --> N133 + N134 --> N58 N134 --> N135 - N136 --> N88 - N136 --> N1 - N136 --> N2 - N137 --> N138 - N139 --> N2 - N139 --> N56 - N139 --> N140 - N139 --> N141 - N139 --> N65 - N139 --> N142 - N139 --> N143 - N139 --> N43 - N139 --> N67 - N139 --> N144 - N139 --> N145 - N139 --> N146 - N147 --> N138 + N136 --> N137 + N136 --> N135 + N138 --> N2 + N138 --> N130 + N138 --> N139 + N138 --> N140 + N138 --> N131 + N138 --> N70 + N138 --> N133 + N138 --> N141 + N133 --> N130 + N133 --> N142 + N143 --> N96 + N143 --> N144 + N145 --> N96 + N145 --> N1 + N145 --> N2 + N146 --> N4 + N147 --> N2 + N147 --> N61 N147 --> N148 - N147 --> N55 - N147 --> N56 - N147 --> N65 - N149 --> N7 - N149 --> N53 - N149 --> N8 - N149 --> N54 - N150 --> N151 - N150 --> N152 - N153 --> N154 - N155 --> N88 - N155 --> N2 - N155 --> N7 - N156 --> N2 - N156 --> N157 - N156 --> N56 - N156 --> N65 - N158 --> N159 + N147 --> N149 + N147 --> N70 + N147 --> N150 + N147 --> N151 + N147 --> N48 + N147 --> N72 + N147 --> N152 + N147 --> N153 + N147 --> N154 + N155 --> N156 + N157 --> N4 + N157 --> N158 + N157 --> N60 + N157 --> N61 + N157 --> N70 + N159 --> N10 + N159 --> N58 + N159 --> N11 + N159 --> N59 N160 --> N161 - N162 --> N163 - N162 --> N164 - N162 --> N165 + N160 --> N162 + N163 --> N96 + N163 --> N2 + N163 --> N10 + N164 --> N2 + N164 --> N165 + N164 --> N61 + N164 --> N70 N166 --> N167 - N166 --> N168 - N166 --> N165 - N169 --> N170 - N169 --> N171 - N169 --> N165 - N172 --> N173 - N172 --> N174 - N172 --> N165 - N175 --> N176 - N175 --> N177 - N175 --> N165 - N178 --> N179 - N178 --> N180 - N178 --> N165 - N181 --> N89 - N181 --> N182 - N181 --> N165 + N168 --> N169 + N170 --> N171 + N170 --> N172 + N170 --> N173 + N174 --> N175 + N174 --> N176 + N174 --> N173 + N177 --> N178 + N177 --> N179 + N177 --> N173 + N180 --> N181 + N180 --> N182 + N180 --> N173 N183 --> N184 - N183 --> N165 - N185 --> N186 - N185 --> N187 - N185 --> N184 - N185 --> N165 - N185 --> N65 - N185 --> N188 - N185 --> N152 + N183 --> N185 + N183 --> N173 + N186 --> N187 + N186 --> N188 + N186 --> N173 + N189 --> N97 N189 --> N190 - N189 --> N191 - N189 --> N165 - N192 --> N193 - N192 --> N194 - N192 --> N165 - N195 --> N196 - N195 --> N197 - N195 --> N165 - N198 --> N199 - N198 --> N200 - N198 --> N165 - N201 --> N202 - N201 --> N203 - N201 --> N165 - N204 --> N26 - N204 --> N205 - N204 --> N206 - N205 --> N87 - N205 --> N88 - N205 --> N2 - N205 --> N26 - N205 --> N206 - N206 --> N87 + N189 --> N173 + N191 --> N192 + N191 --> N173 + N193 --> N194 + N193 --> N195 + N193 --> N192 + N193 --> N173 + N193 --> N70 + N193 --> N196 + N193 --> N162 + N197 --> N198 + N197 --> N199 + N197 --> N173 + N200 --> N201 + N200 --> N202 + N200 --> N173 + N203 --> N204 + N203 --> N205 + N203 --> N173 N206 --> N207 - N208 --> N88 - N208 --> N2 + N206 --> N208 + N206 --> N173 N209 --> N210 - N209 --> N65 - N211 --> N207 + N209 --> N211 + N209 --> N173 N212 --> N213 - N214 --> N215 - N216 --> N111 + N212 --> N214 + N212 --> N173 + N215 --> N29 + N215 --> N216 + N215 --> N217 + N216 --> N95 + N216 --> N96 + N216 --> N2 + N216 --> N29 + N216 --> N217 + N217 --> N95 N217 --> N218 - N217 --> N188 - N219 --> N87 - N219 --> N88 + N219 --> N96 N219 --> N2 - N219 --> N207 - N219 --> N220 - N221 --> N12 - N221 --> N141 - N221 --> N65 - N221 --> N43 - N222 --> N223 - N222 --> N224 + N220 --> N221 + N220 --> N70 + N222 --> N218 + N223 --> N224 N225 --> N226 - N225 --> N227 - N225 --> N228 - N225 --> N54 - N225 --> N43 - N225 --> N229 + N227 --> N120 + N228 --> N229 + N228 --> N196 + N230 --> N95 + N230 --> N96 + N230 --> N2 + N230 --> N218 N230 --> N231 - N230 --> N232 + N232 --> N20 + N232 --> N149 + N232 --> N70 + N232 --> N48 N233 --> N234 N233 --> N235 - N236 --> N88 - N236 --> N2 - N236 --> N26 - N236 --> N56 - N236 --> N141 - N236 --> N65 - N236 --> N142 - N236 --> N143 - N236 --> N43 - N236 --> N67 - N236 --> N145 - N237 --> N238 - N237 --> N239 - N240 --> N226 - N240 --> N89 - N240 --> N56 - N241 --> N87 - N241 --> N88 - N241 --> N2 - N241 --> N220 - N241 --> N89 - N241 --> N92 - N242 --> N88 - N242 --> N2 - N242 --> N186 - N242 --> N196 - N242 --> N148 - N242 --> N141 - N242 --> N65 - N243 --> N244 - N243 --> N12 - N243 --> N89 - N243 --> N43 - N245 --> N246 - N245 --> N247 - N245 --> N190 - N245 --> N248 - N249 --> N250 - N249 --> N251 - N252 --> N253 - N252 --> N43 - N254 --> N87 - N254 --> N88 - N254 --> N2 + N236 --> N237 + N236 --> N238 + N236 --> N239 + N236 --> N59 + N236 --> N48 + N236 --> N240 + N241 --> N242 + N241 --> N243 + N244 --> N245 + N244 --> N246 + N247 --> N96 + N247 --> N2 + N247 --> N29 + N247 --> N61 + N247 --> N149 + N247 --> N70 + N247 --> N150 + N247 --> N151 + N247 --> N48 + N247 --> N72 + N247 --> N153 + N248 --> N249 + N248 --> N250 + N251 --> N237 + N251 --> N97 + N251 --> N61 + N252 --> N95 + N252 --> N96 + N252 --> N2 + N252 --> N231 + N252 --> N97 + N252 --> N100 + N253 --> N96 + N253 --> N2 + N253 --> N194 + N253 --> N204 + N253 --> N158 + N253 --> N149 + N253 --> N70 N254 --> N255 - N254 --> N135 - N256 --> N87 - N256 --> N88 - N256 --> N2 - N256 --> N220 - N257 --> N258 - N257 --> N152 - N259 --> N260 - N259 --> N56 - N259 --> N65 - N259 --> N43 - N261 --> N262 + N254 --> N20 + N254 --> N97 + N254 --> N48 + N256 --> N257 + N256 --> N258 + N256 --> N198 + N256 --> N259 + N260 --> N261 + N260 --> N262 N263 --> N264 - N263 --> N265 - N266 --> N267 - N266 --> N268 - N269 --> N270 - N269 --> N271 - N269 --> N272 - N269 --> N215 - N273 --> N274 - N273 --> N268 - N275 --> N276 - N275 --> N277 - N275 --> N268 - N275 --> N21 - N275 --> N278 - N275 --> N65 - N275 --> N279 - N275 --> N280 - N281 --> N2 - N281 --> N282 - N281 --> N26 - N281 --> N268 - N281 --> N279 - N281 --> N280 - N281 --> N43 - N283 --> N268 - N284 --> N262 - N284 --> N268 - N285 --> N286 - N285 --> N287 - N285 --> N271 - N285 --> N288 - N285 --> N215 - N285 --> N268 - N285 --> N9 - N285 --> N289 - N285 --> N279 - N290 --> N291 - N290 --> N292 - N290 --> N268 - N293 --> N294 - N293 --> N291 - N295 --> N286 - N295 --> N287 + N263 --> N48 + N265 --> N95 + N265 --> N96 + N265 --> N2 + N265 --> N266 + N265 --> N144 + N267 --> N95 + N267 --> N96 + N267 --> N2 + N267 --> N231 + N268 --> N269 + N268 --> N162 + N270 --> N271 + N270 --> N61 + N270 --> N70 + N270 --> N48 + N272 --> N273 + N274 --> N275 + N274 --> N276 + N277 --> N257 + N277 --> N278 + N277 --> N213 + N277 --> N279 + N280 --> N281 + N280 --> N282 + N283 --> N284 + N283 --> N285 + N283 --> N286 + N283 --> N226 + N287 --> N288 + N287 --> N282 + N289 --> N290 + N289 --> N291 + N289 --> N282 + N289 --> N17 + N289 --> N292 + N289 --> N70 + N289 --> N293 + N289 --> N294 + N295 --> N2 N295 --> N296 - N295 --> N215 - N295 --> N268 - N295 --> N9 - N295 --> N289 - N297 --> N268 - N298 --> N299 - N298 --> N300 - N298 --> N286 - N298 --> N287 - N298 --> N301 - N298 --> N272 - N298 --> N215 - N298 --> N268 - N298 --> N9 - N298 --> N289 - N302 --> N303 - N302 --> N286 - N302 --> N304 - N302 --> N305 - N302 --> N272 - N302 --> N215 - N302 --> N268 - N302 --> N306 - N307 --> N12 - N308 --> N271 - N308 --> N268 - N308 --> N309 - N308 --> N310 - N308 --> N311 - N312 --> N268 - N313 --> N12 - N313 --> N291 - N313 --> N314 - N315 --> N268 - N315 --> N316 - N317 --> N268 - N318 --> N274 - N318 --> N268 - N319 --> N303 - N319 --> N271 - N319 --> N320 - N319 --> N215 - N319 --> N268 - N319 --> N309 - N319 --> N306 - N321 --> N291 - N322 --> N271 + N295 --> N29 + N295 --> N282 + N295 --> N293 + N295 --> N294 + N295 --> N48 + N297 --> N282 + N298 --> N273 + N298 --> N282 + N299 --> N300 + N299 --> N301 + N299 --> N285 + N299 --> N302 + N299 --> N226 + N299 --> N282 + N299 --> N12 + N299 --> N303 + N299 --> N293 + N304 --> N305 + N304 --> N306 + N304 --> N282 + N307 --> N308 + N307 --> N305 + N309 --> N300 + N309 --> N301 + N309 --> N310 + N309 --> N226 + N309 --> N282 + N309 --> N12 + N309 --> N303 + N311 --> N282 + N312 --> N313 + N312 --> N314 + N312 --> N300 + N312 --> N301 + N312 --> N315 + N312 --> N286 + N312 --> N226 + N312 --> N282 + N312 --> N12 + N312 --> N303 + N316 --> N317 + N316 --> N300 + N316 --> N318 + N316 --> N319 + N316 --> N286 + N316 --> N226 + N316 --> N282 + N316 --> N320 + N321 --> N20 + N322 --> N285 + N322 --> N282 N322 --> N323 - N324 --> N12 - N324 --> N291 - N324 --> N325 - N324 --> N326 - N324 --> N286 - N324 --> N271 - N324 --> N327 - N324 --> N328 - N324 --> N215 - N324 --> N268 - N329 --> N330 - N329 --> N309 - N329 --> N331 - N329 --> N332 - N329 --> N324 - N333 --> N334 - N333 --> N268 - N333 --> N309 - N333 --> N335 - N333 --> N336 - N333 --> N337 - N333 --> N338 - N339 --> N271 - N339 --> N268 - N339 --> N309 - N340 --> N26 - N340 --> N341 - N340 --> N342 - N340 --> N343 - N340 --> N287 - N340 --> N271 - N340 --> N344 - N340 --> N345 - N340 --> N346 - N340 --> N215 - N340 --> N268 - N340 --> N347 - N340 --> N289 + N322 --> N324 + N322 --> N325 + N326 --> N327 + N326 --> N282 + N328 --> N20 + N328 --> N305 + N328 --> N329 + N330 --> N327 + N330 --> N282 + N330 --> N331 + N332 --> N333 + N332 --> N334 + N332 --> N335 + N332 --> N336 + N332 --> N337 + N332 --> N338 + N332 --> N282 + N332 --> N339 + N332 --> N340 + N332 --> N341 + N332 --> N342 + N332 --> N343 + N344 --> N282 + N345 --> N288 + N345 --> N282 + N346 --> N317 + N346 --> N285 + N346 --> N347 + N346 --> N226 + N346 --> N282 + N346 --> N323 + N346 --> N320 + N348 --> N305 + N348 --> N333 + N348 --> N334 + N348 --> N335 + N348 --> N336 + N348 --> N285 N348 --> N349 - N348 --> N268 + N348 --> N338 + N348 --> N327 + N348 --> N282 + N348 --> N323 + N348 --> N350 + N348 --> N341 + N348 --> N342 + N348 --> N343 + N351 --> N285 + N351 --> N352 + N353 --> N20 + N353 --> N305 + N353 --> N333 + N353 --> N336 + N353 --> N300 + N353 --> N285 + N353 --> N354 + N353 --> N338 + N353 --> N355 + N353 --> N226 + N353 --> N282 + N356 --> N357 + N356 --> N338 + N356 --> N323 + N356 --> N350 + N356 --> N358 + N356 --> N343 + N356 --> N353 + N359 --> N360 + N359 --> N338 + N359 --> N282 + N359 --> N323 + N359 --> N339 + N359 --> N340 + N359 --> N361 + N359 --> N343 + N359 --> N362 + N363 --> N285 + N363 --> N327 + N363 --> N282 + N363 --> N323 + N364 --> N29 + N364 --> N365 + N364 --> N366 + N364 --> N367 + N364 --> N301 + N364 --> N285 + N364 --> N368 + N364 --> N369 + N364 --> N370 + N364 --> N226 + N364 --> N282 + N364 --> N341 + N364 --> N303 + N371 --> N372 + N371 --> N282 ``` diff --git a/docs/architecture/indexion-dependency-graph-04-server-application-components.md b/docs/architecture/indexion-dependency-graph-04-server-application-components.md index 156367b36..e77680662 100644 --- a/docs/architecture/indexion-dependency-graph-04-server-application-components.md +++ b/docs/architecture/indexion-dependency-graph-04-server-application-components.md @@ -76,38 +76,35 @@ graph LR N71[npm:node:fs] N72[npm:node:path] N73[apps/server/node_modules/zod/index.d.cts] - N74[app.css] - N75[url:] - N76[router.tsx] - N77[apps/server/node_modules/@solid-imager/client/src/index.ts] - N78[npm:@solid-imager/ui/query-options] - N79[routeTree.gen.ts] - N80[apps/server/src/routes/__root.tsx] - N81[apps/server/src/routes/search.tsx] - N82[apps/server/src/routes/manager.tsx] - N83[apps/server/src/routes/design-lab.tsx] - N84[apps/server/src/routes/config.tsx] - N85[apps/server/src/routes/about.tsx] - N86[apps/server/src/routes/$.tsx] - N87[apps/server/src/routes/v2/route.tsx] - N88[apps/server/src/routes/index.tsx] - N89[apps/server/src/routes/v2/index.tsx] - N90[apps/server/src/routes/sources/index.tsx] - N91[apps/server/src/routes/v2/search.tsx] - N92[apps/server/src/routes/v2/manager.tsx] - N93[apps/server/src/routes/v2/jobs.tsx] - N94[apps/server/src/routes/v2/config.tsx] - N95[apps/server/src/routes/v2/about.tsx] - N96[apps/server/src/routes/v2/$.tsx] - N97[apps/server/src/routes/sources/$mediaSourceId/index.tsx] - N98[apps/server/src/routes/docs/swagger/index.tsx] - N99[apps/server/src/routes/api/rpc.$.ts] - N100[apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx] - N101[apps/server/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] - N102[apps/server/src/routes/api/sources.$mediaSourceId.$mediaId.ts] - N103[apps/server/src/routes/api/jobs.$jobId.artifact.ts] - N104[apps/server/src/routes/v2/sources/$mediaSourceId/$mediaId/index.tsx] - N105[apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts] + N74[routeTree.gen.ts] + N75[apps/server/src/routes/__root.tsx] + N76[apps/server/src/routes/search.tsx] + N77[apps/server/src/routes/manager.tsx] + N78[apps/server/src/routes/design-lab.tsx] + N79[apps/server/src/routes/config.tsx] + N80[apps/server/src/routes/about.tsx] + N81[apps/server/src/routes/$.tsx] + N82[apps/server/src/routes/v2/route.tsx] + N83[apps/server/src/routes/index.tsx] + N84[apps/server/src/routes/v2/index.tsx] + N85[apps/server/src/routes/sources/index.tsx] + N86[apps/server/src/routes/v2/search.tsx] + N87[apps/server/src/routes/v2/manager.tsx] + N88[apps/server/src/routes/v2/jobs.tsx] + N89[apps/server/src/routes/v2/config.tsx] + N90[apps/server/src/routes/v2/about.tsx] + N91[apps/server/src/routes/v2/$.tsx] + N92[apps/server/src/routes/sources/$mediaSourceId/index.tsx] + N93[apps/server/src/routes/docs/swagger/index.tsx] + N94[apps/server/src/routes/api/rpc.$.ts] + N95[apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx] + N96[apps/server/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] + N97[apps/server/src/routes/api/sources.$mediaSourceId.$mediaId.ts] + N98[apps/server/src/routes/api/jobs.$jobId.artifact.ts] + N99[apps/server/src/routes/v2/sources/$mediaSourceId/$mediaId/index.tsx] + N100[apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts] + N101[app.css] + N102[url:] N0 --> N1 N0 --> N2 N2 --> N3 @@ -199,32 +196,30 @@ graph LR N70 --> N72 N70 --> N73 N74 --> N75 - N76 --> N77 - N76 --> N78 - N79 --> N80 - N79 --> N81 - N79 --> N82 - N79 --> N83 - N79 --> N84 - N79 --> N85 - N79 --> N86 - N79 --> N87 - N79 --> N88 - N79 --> N89 - N79 --> N90 - N79 --> N91 - N79 --> N92 - N79 --> N93 - N79 --> N94 - N79 --> N95 - N79 --> N96 - N79 --> N97 - N79 --> N98 - N79 --> N99 - N79 --> N100 - N79 --> N101 - N79 --> N102 - N79 --> N103 - N79 --> N104 - N79 --> N105 + N74 --> N76 + N74 --> N77 + N74 --> N78 + N74 --> N79 + N74 --> N80 + N74 --> N81 + N74 --> N82 + N74 --> N83 + N74 --> N84 + N74 --> N85 + N74 --> N86 + N74 --> N87 + N74 --> N88 + N74 --> N89 + N74 --> N90 + N74 --> N91 + N74 --> N92 + N74 --> N93 + N74 --> N94 + N74 --> N95 + N74 --> N96 + N74 --> N97 + N74 --> N98 + N74 --> N99 + N74 --> N100 + N101 --> N102 ``` diff --git a/docs/architecture/indexion-dependency-graph-05-ui.md b/docs/architecture/indexion-dependency-graph-05-ui.md index a201c23c2..73e579b5f 100644 --- a/docs/architecture/indexion-dependency-graph-05-ui.md +++ b/docs/architecture/indexion-dependency-graph-05-ui.md @@ -214,6 +214,9 @@ graph LR N209[v2-upload-media-modal-content.tsx] N210[upload-media-modal-content.types.ts] N211[v2-upload-media-modal.tsx] + N212[search-history-client.ts] + N213[npm:@solid-imager/core/domain/contract/search-snapshots-client] + N214[search-history-route.ts] N0 --> N1 N0 --> N2 N0 --> N3 @@ -627,4 +630,6 @@ graph LR N211 --> N85 N211 --> N2 N211 --> N86 + N212 --> N213 + N214 --> N86 ``` diff --git a/docs/architecture/indexion-dependency-graph-full.md b/docs/architecture/indexion-dependency-graph-full.md index 6969da7d3..8198e140e 100644 --- a/docs/architecture/indexion-dependency-graph-full.md +++ b/docs/architecture/indexion-dependency-graph-full.md @@ -20,931 +20,961 @@ graph LR N15[apps/cli/src/index.ts] N16[apps/cli/node_modules/@solid-imager/client/src/index.ts] N17[npm:@solid-imager/core/utils] - N18[apps/server/src/app.css] - N19[url:] - N20[apps/server/src/components/imports/pending-downloads-indicator.tsx] - N21[npm:@solid-imager/ui/pending-downloads-indicator] - N22[apps/server/src/components/imports/pending-downloads-indicator-data.ts] - N23[npm:@solid-imager/ui/event-stream] - N24[apps/server/src/components/imports/v2-pending-downloads-indicator.tsx] - N25[npm:@solid-imager/ui/v2-pending-downloads-indicator] - N26[apps/server/src/components/media/ai-tagging-modal.tsx] - N27[npm:@solid-imager/ui/ai-tagging-modal] - N28[npm:~/infrastructure/api-clients/ai-api] - N29[apps/server/src/components/media/association-manager.tsx] - N30[npm:@solid-imager/ui/badge] - N31[npm:@solid-imager/ui/button] - N32[apps/server/src/components/media/bulk-action-dialog.tsx] - N33[npm:@solid-imager/core/domain/sources/schemas] - N34[apps/server/src/components/media/character-crop-modal.tsx] - N35[npm:@solid-imager/core/domain/media/schemas] - N36[npm:@solid-imager/ui/character-crop-modal] - N37[apps/server/src/components/media/search-filters.tsx] - N38[npm:@solid-imager/core/domain/authors/schemas] - N39[npm:@solid-imager/core/domain/characters/schemas] - N40[npm:@solid-imager/core/domain/ips/schemas] - N41[npm:@solid-imager/core/domain/projects/schemas] - N42[npm:@solid-imager/core/domain/tags/schemas] - N43[apps/server/src/components/media/media-viewer.tsx] - N44[apps/server/src/components/media/move-copy-media-dialog.tsx] - N45[npm:@solid-imager/ui/move-copy-media-dialog] - N46[apps/server/node_modules/solid-js/types/index.d.ts] - N47[npm:~/infrastructure/api-clients/sources-api] - N48[apps/server/src/components/media/preset-manager.tsx] - N49[npm:@solid-imager/ui/preset-client] - N50[npm:@solid-imager/ui/preset-manager] - N51[npm:~/infrastructure/api/clients/preset-client] - N52[apps/server/src/components/media/pro-search-builder.tsx] - N53[apps/server/src/components/media/pro-search-dialog.tsx] - N54[apps/server/src/components/media/search-control-panel.tsx] - N55[npm:@solid-imager/ui/label] - N56[apps/server/src/components/media/sort-controls.tsx] - N57[apps/server/src/components/media/thumbnail-image.tsx] - N58[npm:@solid-imager/ui/thumbnail-image] - N59[apps/server/src/components/media/oppai-oracle-modal.tsx] - N60[npm:@solid-imager/ui/oppai-oracle-modal] - N61[apps/server/src/components/media/legacy-media-grid-item.tsx] - N62[apps/server/src/components/media/legacy-media-sidebar.tsx] - N63[npm:@solid-imager/ui/clipboard-copy] - N64[npm:@solid-imager/ui/collapsible] - N65[npm:@solid-imager/ui/stores/search-store] - N66[npm:@solid-imager/ui/toast] - N67[apps/server/node_modules/@tanstack/solid-query/build/index.cjs] - N68[apps/server/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] - N69[apps/server/src/components/media/v2-media-actions.tsx] - N70[apps/server/src/components/media/v2-media-sidebar.tsx] - N71[apps/server/src/components/media/v2-media-viewer.tsx] - N72[apps/server/src/components/nav.tsx] - N73[npm:@solid-imager/ui/layouts/app-nav] - N74[apps/server/src/components/simple-modal.tsx] - N75[apps/server/src/components/swagger-ui.tsx] - N76[apps/server/node_modules/swagger-ui-dist/swagger-ui-bundle.js] - N77[apps/server/node_modules/swagger-ui-dist/swagger-ui.css] - N78[apps/server/src/components/upload-media-modal.tsx] - N79[npm:@solid-imager/ui/legacy-upload-media-modal-content] - N80[npm:~/infrastructure/api-clients/fetch-url-api] - N81[apps/server/src/components/api-activity-indicator.tsx] - N82[apps/server/src/components/v2-upload-media-modal.tsx] - N83[npm:@solid-imager/ui/v2-upload-media-modal-content] - N84[apps/server/src/components/v2/v2-mobile-header.tsx] - N85[npm:@solid-imager/ui/v2/icons] - N86[npm:~/components/imports/v2-pending-downloads-indicator] - N87[apps/server/src/components/v2/v2-sidebar.tsx] - N88[apps/server/src/components/v2/v2-source-list.tsx] - N89[apps/server/src/config/database.ts] - N90[apps/server/node_modules/zod/index.d.cts] - N91[apps/server/src/infrastructure/ai/rust-ai-client.ts] - N92[npm:node:os] - N93[apps/server/node_modules/@solid-imager/client/src/index.ts] - N94[npm:@solid-imager/core/domain/contract] - N95[npm:@solid-imager/core/domain/interfaces/ai-client] - N96[apps/server/src/infrastructure/api-clients/ai-api.ts] - N97[npm:@solid-imager/core/domain/tagging/schemas] - N98[npm:~/infrastructure/api-clients/orpc-client] - N99[apps/server/src/infrastructure/api-clients/characters-api.ts] - N100[apps/server/src/infrastructure/api-clients/downloads-api.ts] - N101[apps/server/src/infrastructure/api-clients/fetch-url-api.ts] - N102[apps/server/src/infrastructure/api-clients/ips-api.ts] - N103[apps/server/src/infrastructure/api-clients/media-api.ts] - N104[apps/server/src/infrastructure/api-clients/search-api.ts] - N105[apps/server/src/infrastructure/api-clients/orpc-client.ts] - N106[apps/server/node_modules/@orpc/server/dist/index.d.mts] - N107[npm:@tanstack/solid-start] - N108[npm:@tanstack/solid-start/server] - N109[npm:~/infrastructure/api/app-router] - N110[apps/server/src/infrastructure/api-clients/projects-api.ts] - N111[apps/server/src/infrastructure/api-clients/queries/index.ts] - N112[apps/server/node_modules/@orpc/solid-query/dist/index.d.mts] - N113[apps/server/src/infrastructure/api-clients/sources-api.ts] - N114[apps/server/src/infrastructure/api-clients/thumbnails.ts] - N115[apps/server/src/infrastructure/api/clients/preset-client.ts] - N116[npm:@solid-imager/core/domain/contract/presets-client] - N117[apps/server/src/infrastructure/api/routers/ai-router.ts] - N118[apps/server/src/infrastructure/api/routers/authors-router.ts] - N119[npm:@solid-imager/core/domain/contract/authors.contract] - N120[npm:~/infrastructure/repositories/authors-repository] - N121[apps/server/src/infrastructure/api/routers/categories-router.ts] - N122[npm:@solid-imager/core/domain/contract/categories.contract] - N123[npm:~/infrastructure/services/category-service] - N124[apps/server/src/infrastructure/api/routers/characters-router.ts] - N125[npm:@solid-imager/core/domain/contract/characters.contract] - N126[npm:~/infrastructure/services/character-service] - N127[apps/server/src/infrastructure/api/routers/entity-media-counts.ts] - N128[apps/server/src/infrastructure/api/routers/config-router.ts] - N129[npm:@solid-imager/core/domain/contract/config.contract] - N130[npm:~/infrastructure/service-registry] - N131[apps/server/src/infrastructure/api/routers/directories-router.ts] - N132[npm:@solid-imager/core/domain/contract/directories.contract] - N133[npm:~/infrastructure/services/directory-service] - N134[apps/server/src/infrastructure/api/routers/downloads-router.ts] - N135[npm:@solid-imager/core/domain/contract/downloads.contract] - N136[npm:~/infrastructure/jobs/download-jobs] - N137[apps/server/src/infrastructure/api/routers/imports-router.ts] - N138[npm:@solid-imager/core/domain/contract/imports.contract] - N139[npm:@solid-imager/core/domain/sources/events] - N140[apps/server/node_modules/drizzle-orm/index.d.ts] - N141[npm:~/infrastructure/db] - N142[npm:~/infrastructure/db/schema] - N143[npm:~/infrastructure/events/realtime-event-bus] - N144[npm:~/infrastructure/services/backup-service] - N145[apps/server/src/infrastructure/api/routers/ips-router.ts] - N146[npm:@solid-imager/core/domain/contract/ips.contract] - N147[npm:~/infrastructure/services/ip-service] - N148[apps/server/src/infrastructure/api/routers/media-router.ts] - N149[npm:@solid-imager/core/domain/contract/media.contract] - N150[npm:@solid-imager/core/domain/errors] - N151[npm:@solid-imager/core/utils/async-pool] - N152[npm:~/infrastructure/logger] - N153[npm:~/infrastructure/services/bulk-operation-service] - N154[npm:~/infrastructure/services/ccip-vector-service] - N155[npm:~/infrastructure/services/media-service] - N156[apps/server/src/infrastructure/api/routers/presets-router.ts] - N157[npm:@solid-imager/core/domain/contract/presets.contract] - N158[npm:~/infrastructure/services/preset-service] - N159[apps/server/src/infrastructure/api/routers/projects-router.ts] - N160[npm:@solid-imager/core/domain/contract/projects.contract] - N161[npm:~/infrastructure/services/project-service] - N162[apps/server/src/infrastructure/api/routers/sources-router.ts] - N163[apps/server/src/infrastructure/api/routers/tags-router.ts] - N164[npm:@solid-imager/core/domain/contract/tags.contract] - N165[npm:~/infrastructure/services/tag-service] - N166[apps/server/src/infrastructure/api/routers/thumbnails-router.ts] - N167[npm:@solid-imager/core/domain/contract/thumbnails.contract] - N168[npm:~/infrastructure/services/thumbnail-service] - N169[apps/server/src/infrastructure/api/routers/utils-router.ts] - N170[npm:@solid-imager/core/domain/contract/utils.contract] - N171[apps/server/src/infrastructure/api/routers/jobs-router.ts] - N172[npm:@solid-imager/core/domain/contract/jobs.contract] - N173[apps/server/src/infrastructure/api/job-artifact.ts] - N174[npm:node:fs/promises] - N175[npm:@solid-imager/core/domain/repositories/job-repository] - N176[npm:~/infrastructure/repositories/job-repository] - N177[npm:~/infrastructure/services/job-transfer-storage] - N178[npm:~/infrastructure/utils/stream-utils] - N179[apps/server/src/infrastructure/api/app-router.ts] - N180[npm:~/infrastructure/api/routers/ai-router] - N181[npm:~/infrastructure/api/routers/authors-router] - N182[npm:~/infrastructure/api/routers/categories-router] - N183[npm:~/infrastructure/api/routers/characters-router] - N184[npm:~/infrastructure/api/routers/config-router] - N185[npm:~/infrastructure/api/routers/directories-router] - N186[npm:~/infrastructure/api/routers/downloads-router] - N187[npm:~/infrastructure/api/routers/imports-router] - N188[npm:~/infrastructure/api/routers/ips-router] - N189[npm:~/infrastructure/api/routers/jobs-router] - N190[npm:~/infrastructure/api/routers/media-router] - N191[npm:~/infrastructure/api/routers/presets-router] - N192[npm:~/infrastructure/api/routers/projects-router] - N193[npm:~/infrastructure/api/routers/sources-router] - N194[npm:~/infrastructure/api/routers/tags-router] - N195[npm:~/infrastructure/api/routers/thumbnails-router] - N196[npm:~/infrastructure/api/routers/utils-router] - N197[apps/server/src/infrastructure/bootstrap.ts] - N198[npm:~/infrastructure/ai/rust-ai-client] - N199[npm:~/infrastructure/db/transaction-manager] - N200[npm:~/infrastructure/file-system/node-file-system] - N201[npm:~/infrastructure/jobs/download-rate-limiter] - N202[npm:~/infrastructure/jobs/job-worker] - N203[apps/server/src/infrastructure/db/__mocks__/index.ts] - N204[npm:uuid] - N205[apps/server/node_modules/vitest/dist/index.js] - N206[apps/server/src/infrastructure/db/connection.ts] - N207[apps/server/node_modules/@electric-sql/pglite/dist/index.cjs] - N208[apps/server/node_modules/pg/esm/index.mjs] - N209[npm:~/config/database] - N210[apps/server/src/infrastructure/db/pglite.ts] - N211[apps/server/src/infrastructure/db/data-migration.ts] - N212[npm:~/infrastructure/db/index] - N213[apps/server/src/infrastructure/db/executor.ts] - N214[npm:@solid-imager/db/types] - N215[apps/server/src/infrastructure/db/index.ts] - N216[apps/server/node_modules/drizzle-orm/node-postgres/index.d.ts] - N217[apps/server/node_modules/drizzle-orm/pglite/index.d.ts] - N218[apps/server/src/infrastructure/db/schema.ts] - N219[apps/server/node_modules/@electric-sql/pglite-pgvector/dist/index.cjs] - N220[apps/server/src/infrastructure/file-system/node-file-system.ts] - N221[apps/server/node_modules/@solid-imager/core/src/index.ts] - N222[apps/server/src/infrastructure/jobs/download-jobs.ts] - N223[apps/server/src/infrastructure/jobs/download-rate-limiter.ts] - N224[npm:@solid-imager/core/domain/config/config-schema] - N225[apps/server/src/infrastructure/jobs/file-watcher-service.ts] - N226[npm:~/infrastructure/jobs/file-watcher-manager] - N227[npm:~/infrastructure/jobs/thumbnails] - N228[npm:~/infrastructure/repositories/media-repository] - N229[npm:~/infrastructure/repositories/source-repository] - N230[npm:~/infrastructure/services/directory-sync-service] - N231[npm:~/infrastructure/services/media-processing-service] - N232[npm:~/infrastructure/storage/server-media-storage] - N233[apps/server/src/infrastructure/jobs/job-worker.ts] - N234[npm:~/domain/repositories/job-repository] - N235[apps/server/src/infrastructure/jobs/tagging-jobs.ts] - N236[apps/server/src/infrastructure/jobs/tag-extraction.ts] - N237[npm:~/infrastructure/processing/image-processor] - N238[npm:~/infrastructure/repositories/tag-repository] - N239[apps/server/src/infrastructure/jobs/ccip-jobs.ts] - N240[npm:@solid-imager/application/ports/ccip-vector-store] - N241[apps/server/src/infrastructure/jobs/thumbnails.ts] - N242[apps/server/src/infrastructure/jobs/file-watcher-manager.ts] - N243[apps/server/node_modules/chokidar/index.js] - N244[apps/server/src/infrastructure/logger.ts] - N245[apps/server/node_modules/pino/pino.js] - N246[apps/server/src/infrastructure/processing/image-processor.ts] - N247[apps/server/node_modules/sharp/dist/index.cjs] - N248[apps/server/src/infrastructure/repositories/author-repository.ts] - N249[npm:@solid-imager/core/domain/repositories/author-repository] - N250[npm:@solid-imager/db/repositories/author-repository] - N251[npm:~/infrastructure/db/executor] - N252[apps/server/src/infrastructure/repositories/authors-repository.ts] - N253[npm:@solid-imager/core/domain/repositories/authors-repository] - N254[npm:@solid-imager/db/repositories/authors-repository] - N255[apps/server/src/infrastructure/repositories/category-repository.ts] - N256[npm:@solid-imager/core/domain/repositories/category-repository] - N257[npm:@solid-imager/db/repositories/category-repository] - N258[apps/server/src/infrastructure/repositories/character-repository.ts] - N259[npm:@solid-imager/core/domain/repositories/character-repository] - N260[npm:@solid-imager/db/repositories/character-repository] - N261[apps/server/src/infrastructure/repositories/collection-repository.ts] - N262[npm:@solid-imager/core/domain/repositories/collection-repository] - N263[npm:@solid-imager/db/repositories/collection-repository] - N264[apps/server/src/infrastructure/repositories/ip-repository.ts] - N265[npm:@solid-imager/core/domain/repositories/ip-repository] - N266[npm:@solid-imager/db/repositories/ip-repository] - N267[apps/server/src/infrastructure/repositories/job-repository.ts] - N268[npm:@solid-imager/db/repositories/job-repository] - N269[apps/server/src/infrastructure/repositories/media-repository-utils.ts] - N270[npm:@solid-imager/db/repositories/media-repository-utils] - N271[apps/server/src/infrastructure/repositories/media-repository.ts] - N272[npm:@solid-imager/core/domain/repositories/media-repository] - N273[npm:@solid-imager/db/repositories/media-repository] - N274[npm:~/infrastructure/repositories/author-repository] - N275[apps/server/src/infrastructure/repositories/preset-repository.ts] - N276[npm:@solid-imager/core/domain/repositories/preset-repository] - N277[npm:@solid-imager/db/repositories/preset-repository] - N278[apps/server/src/infrastructure/repositories/project-repository.ts] - N279[npm:@solid-imager/core/domain/repositories/project-repository] - N280[npm:@solid-imager/db/repositories/project-repository] - N281[apps/server/src/infrastructure/repositories/source-repository.ts] - N282[npm:@solid-imager/core/domain/repositories/source-repository] - N283[npm:@solid-imager/db/repositories/source-repository] - N284[apps/server/src/infrastructure/repositories/tag-repository.ts] - N285[npm:@solid-imager/core/domain/repositories/tag-repository] - N286[npm:@solid-imager/db/repositories/tag-repository] - N287[apps/server/src/infrastructure/repositories/user-repository.ts] - N288[npm:@solid-imager/core/domain/repositories/user-repository] - N289[npm:@solid-imager/db/repositories/user-repository] - N290[apps/server/src/infrastructure/storage/factory.ts] - N291[apps/server/src/infrastructure/storage/local.ts] - N292[apps/server/src/infrastructure/storage/schema.ts] - N293[apps/server/src/infrastructure/storage/server-media-storage.ts] - N294[apps/server/src/infrastructure/utils/ffmpeg.ts] - N295[apps/server/node_modules/fluent-ffmpeg/index.js] - N296[apps/server/src/infrastructure/utils/stream-utils.ts] - N297[apps/server/src/infrastructure/events/realtime-event-bus.ts] - N298[npm:node:events] - N299[apps/server/src/infrastructure/router/route-types.ts] - N300[apps/server/src/infrastructure/server-route-bootstrap.ts] - N301[apps/server/src/infrastructure/services/author-service.ts] - N302[npm:@solid-imager/application/services/author-service] - N303[apps/server/src/infrastructure/services/backup-service.ts] - N304[apps/server/src/infrastructure/services/bulk-operation-service.ts] - N305[apps/server/src/infrastructure/services/category-service.ts] - N306[npm:@solid-imager/application/services/category-service] - N307[npm:~/infrastructure/repositories/category-repository] - N308[apps/server/src/infrastructure/services/ccip-vector-service.ts] - N309[npm:@solid-imager/application/ports/media-service] - N310[npm:@solid-imager/application/services/ccip-vector-service] - N311[npm:~/infrastructure/ai/postgres-ccip-vector-store] - N312[npm:~/infrastructure/services/tagging-service] - N313[apps/server/src/infrastructure/services/collection-service.ts] - N314[npm:@solid-imager/application/services/collection-service] - N315[npm:~/infrastructure/repositories/collection-repository] - N316[apps/server/src/infrastructure/services/directory-service.ts] - N317[npm:~/infrastructure/services/media-source-service] - N318[npm:~/infrastructure/storage/factory] - N319[apps/server/src/infrastructure/services/directory-sync-service.ts] - N320[apps/server/src/infrastructure/services/ip-service.ts] - N321[npm:@solid-imager/application/services/ip-service] - N322[npm:~/infrastructure/repositories/ip-repository] - N323[apps/server/src/infrastructure/services/job-dispatch-service.ts] - N324[apps/server/src/infrastructure/services/job-transfer-storage.ts] - N325[apps/server/src/infrastructure/services/maintenance-service.ts] - N326[apps/server/src/infrastructure/services/media-processing-service.ts] - N327[npm:@solid-imager/core/domain/interfaces/transaction-manager] - N328[apps/server/src/infrastructure/services/preset-service.ts] - N329[apps/server/node_modules/@solid-imager/application/src/index.ts] - N330[npm:@solid-imager/application/services/preset-service] - N331[npm:~/infrastructure/repositories/preset-repository] - N332[apps/server/src/infrastructure/services/project-service.ts] - N333[npm:@solid-imager/application/services/project-service] - N334[npm:~/infrastructure/repositories/project-repository] - N335[apps/server/src/infrastructure/services/search-service.ts] - N336[npm:@solid-imager/application/services/search-service] - N337[apps/server/src/infrastructure/services/server-config-service.ts] - N338[npm:node:util] - N339[apps/server/src/infrastructure/services/source-transfer-job-service.ts] - N340[apps/server/src/infrastructure/services/tag-service.ts] - N341[npm:@solid-imager/application/services/tag-service] - N342[apps/server/src/infrastructure/services/tagging-service.ts] - N343[npm:@solid-imager/application/services/tagging-service] - N344[apps/server/src/infrastructure/services/thumbnail-service.ts] - N345[npm:@solid-imager/core/domain/thumbnails/schemas] - N346[apps/server/src/infrastructure/services/user-service.ts] - N347[npm:@solid-imager/application/services/user-service] - N348[npm:~/infrastructure/repositories/user-repository] - N349[apps/server/src/router.tsx] - N350[npm:@solid-imager/ui/query-options] - N351[apps/server/src/routes/$.tsx] - N352[npm:@solid-imager/ui/screens/not-found-screen] - N353[apps/server/src/routes/__root.tsx] - N354[npm:@solid-imager/ui/layouts/app-shell] - N355[npm:@solid-imager/ui/router-status] - N356[apps/server/src/routes/about.tsx] - N357[npm:@solid-imager/ui/counter] - N358[apps/server/src/routes/api/rpc.$.ts] - N359[npm:@orpc/server/fetch] - N360[npm:@orpc/server/plugins] - N361[npm:~/infrastructure/api/rpc-response-headers] - N362[npm:~/infrastructure/router/route-types] - N363[npm:~/infrastructure/server-route-bootstrap] - N364[apps/server/src/routes/api/sources.$mediaSourceId.$mediaId.ts] - N365[npm:@solid-imager/core/domain/media/utils/media-type-utils] - N366[apps/server/src/routes/api/jobs.$jobId.artifact.ts] - N367[apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts] - N368[apps/server/src/routes/config.tsx] - N369[npm:@solid-imager/ui/query-state] - N370[npm:@solid-imager/ui/screens/legacy-config-state-screen] - N371[npm:~/infrastructure/api-clients/queries] - N372[apps/server/src/routes/v2/$.tsx] - N373[apps/server/src/routes/v2/about.tsx] - N374[apps/server/src/routes/v2/config.tsx] - N375[npm:@solid-imager/ui/screens/v2-config-state-screen] - N376[apps/server/src/routes/v2/index.tsx] - N377[apps/server/src/routes/v2/jobs.tsx] - N378[npm:@solid-imager/core/domain/jobs/schemas] - N379[npm:@solid-imager/ui/hooks/use-job-events] - N380[npm:@solid-imager/ui/screens/v2-jobs-screen] - N381[apps/server/src/routes/v2/manager.tsx] - N382[npm:@solid-imager/ui/hooks/use-manager-page] - N383[npm:@solid-imager/ui/screens/v2-manager/types] - N384[npm:@solid-imager/ui/screens/v2-manager-screen] - N385[npm:~/hooks/use-batch-job-events] - N386[apps/server/src/routes/v2/media-context.ts] - N387[apps/server/src/routes/v2/route.tsx] - N388[npm:~/components/api-activity-indicator] - N389[npm:~/components/v2/v2-app-shell] - N390[apps/server/src/routes/v2/search.tsx] - N391[apps/server/src/routes/v2/sources/$mediaSourceId/$mediaId/index.tsx] - N392[npm:@solid-imager/ui/screens/v2-media-detail-screen] - N393[apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx] - N394[npm:~/routes/sources/$mediaSourceId/components/v2-source-media-page] - N395[apps/server/src/routes/docs/swagger/index.tsx] - N396[apps/server/src/routes/index.tsx] - N397[apps/server/src/routes/manager.tsx] - N398[npm:@solid-imager/ui/screens/manager-screen] - N399[apps/server/src/routes/search.tsx] - N400[apps/server/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] - N401[npm:@solid-imager/ui/screens/legacy-media-detail-screen] - N402[apps/server/src/routes/sources/$mediaSourceId/components/source-media-page.tsx] - N403[npm:@solid-imager/ui/hooks/use-current-search-persistence] - N404[npm:@solid-imager/ui/screens/source-media-screen.types] - N405[npm:@solid-imager/ui/source-media-page] - N406[apps/server/src/routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx] - N407[npm:@solid-imager/ui/screens/source-media-screen] - N408[npm:~/components/media/legacy-media-grid-item] - N409[npm:~/components/upload-media-modal] - N410[apps/server/src/routes/sources/$mediaSourceId/components/v2-source-media-page.tsx] - N411[npm:@solid-imager/ui/screens/v2-source-media-screen] - N412[npm:~/components/media/thumbnail-image] - N413[npm:~/components/media/v2-media-grid-item] - N414[npm:~/components/v2-upload-media-modal] - N415[npm:~/routes/v2/media-context] - N416[apps/server/src/routes/sources/$mediaSourceId/index.tsx] - N417[apps/server/src/routes/sources/index.tsx] - N418[npm:@solid-imager/ui/hooks/use-sources-events] - N419[npm:@solid-imager/ui/hooks/use-sources-page] - N420[npm:@solid-imager/ui/legacy-source-form-modal] - N421[npm:@solid-imager/ui/screens/sources-screen] - N422[npm:@solid-imager/ui/source-card] - N423[npm:@solid-imager/ui/source-delete-modal] - N424[npm:~/hooks/use-media-source-events] - N425[apps/server/src/routes/design-lab.tsx] - N426[npm:@solid-imager/ui/screens/design-concept-screen] - N427[apps/server/src/tests/api/categories/category-id-test.ts] - N428[apps/server/src/tests/api/categories/index.test.ts] - N429[apps/server/src/tests/api/characters/character-id-test.ts] - N430[apps/server/src/tests/api/ips/ip-id-test.ts] - N431[apps/server/src/tests/api/media/add-media.test.ts] - N432[apps/server/src/tests/api/media/delete-media.test.ts] - N433[apps/server/src/tests/api/media/get-media.test.ts] - N434[apps/server/src/tests/api/media/list-media.test.ts] - N435[apps/server/src/tests/api/tags/index.test.ts] - N436[apps/server/src/tests/api/tags/tag-id-test.ts] - N437[apps/server/src/tests/e2e/app-nav.responsive.spec.ts] - N438[apps/server/node_modules/@playwright/test/index.d.ts] - N439[apps/server/src/tests/e2e/support/test.ts] - N440[apps/server/src/tests/e2e/loading-recovery.spec.ts] - N441[apps/server/src/tests/e2e/media-detail-manager-config.responsive.spec.ts] - N442[apps/server/src/tests/e2e/realtime-preservation.spec.ts] - N443[npm:node:crypto] - N444[apps/server/src/tests/e2e/route-reload.spec.ts] - N445[apps/server/src/tests/e2e/search-pro-dialog.responsive.spec.ts] - N446[apps/server/src/tests/e2e/search-realtime-preservation.responsive.spec.ts] - N447[apps/server/src/tests/e2e/search.responsive.spec.ts] - N448[apps/server/src/tests/e2e/support/fixture.ts] - N449[apps/server/src/tests/e2e/sources-source-media.responsive.spec.ts] - N450[apps/server/src/tests/e2e/ui-components.gallery.spec.ts] - N451[apps/server/src/tests/e2e/ui-gallery/index.html] - N452[url:ja] - N453[url:UTF-8] - N454[url:viewport] - N455[url:width=device-width, initial-scale=1.0] - N456[url:root] - N457[url:module] - N458[url:src.tsx] - N459[apps/server/src/tests/e2e/ui-gallery/src.tsx] - N460[apps/server/src/tests/e2e/ui-gallery/vite.config.ts] - N461[npm:node:url] - N462[apps/server/node_modules/@tailwindcss/vite/dist/index.d.mts] - N463[apps/server/node_modules/vite/dist/node/index.js] - N464[apps/server/node_modules/vite-plugin-solid/dist/cjs/index.cjs] - N465[apps/server/src/tests/e2e/v2-routes.responsive.spec.ts] - N466[apps/server/src/tests/e2e/v2-scroll-restoration.spec.ts] - N467[apps/server/src/tests/integration/backup/backup-service.test.ts] - N468[apps/server/src/tests/integration/backup/performance.test.ts] - N469[apps/server/src/tests/integration/backup/zip-backup.test.ts] - N470[apps/server/src/tests/integration/db/pglite-parity.test.ts] - N471[apps/server/src/tests/integration/media/access-denied-integration.test.ts] - N472[npm:~/infrastructure/repositories/character-repository] - N473[apps/server/src/tests/integration/media/add-media-integration.test.ts] - N474[apps/server/src/tests/integration/media/copy-media-integration.test.ts] - N475[apps/server/src/tests/integration/media/delete-media-integration.test.ts] - N476[apps/server/src/tests/integration/media/get-media-details-integration.test.ts] - N477[apps/server/src/tests/integration/media/get-media-integration.test.ts] - N478[apps/server/src/tests/integration/media/list-media-integration.test.ts] - N479[apps/server/src/tests/integration/media/media-type-handling.test.ts] - N480[apps/server/src/tests/integration/media/register-media-integration.test.ts] - N481[apps/server/src/tests/integration/media/update-media-integration.test.ts] - N482[apps/server/src/tests/integration/queries/search.test.ts] - N483[apps/server/src/tests/integration/repository/author-dedupe.test.ts] - N484[apps/server/node_modules/drizzle-orm/pglite/migrator.d.ts] - N485[apps/server/src/tests/integration/repository/character-repository.test.ts] - N486[apps/server/src/tests/integration/security/backup-security.test.ts] - N487[apps/server/src/tests/integration/security/path-traversal.test.ts] - N488[apps/server/src/tests/integration/ai/postgres-ccip-vector-store.test.ts] - N489[apps/server/src/tests/monorepo-migration.test.ts] - N490[apps/server/src/tests/setup-integration.ts] - N491[apps/server/node_modules/dotenv/lib/main.d.ts] - N492[apps/server/src/tests/setup-unit.ts] - N493[apps/server/src/tests/setup.ts] - N494[apps/server/src/tests/unit/application/registry.test.ts] - N495[apps/server/src/tests/unit/application/services/backup-service.test.ts] - N496[apps/server/src/tests/unit/application/services/character-service.test.ts] - N497[apps/server/src/tests/unit/application/services/directory-sync-service.test.ts] - N498[apps/server/src/tests/unit/application/services/media-service.test.ts] - N499[npm:@solid-imager/application/services/media-query-service] - N500[npm:@solid-imager/application/services/media-transfer-service] - N501[npm:@solid-imager/application/services/media-upload-service] - N502[npm:@solid-imager/core/domain/services/image-processor] - N503[apps/server/src/tests/unit/application/services/ccip-vector-service.test.ts] - N504[apps/server/src/tests/unit/application/services/maintenance-service.test.ts] - N505[apps/server/src/tests/unit/application/services/media-processing-service.test.ts] - N506[apps/server/src/tests/unit/application/services/tagging-service.test.ts] - N507[apps/server/src/tests/unit/application/services/job-dispatch-service.test.ts] - N508[apps/server/src/tests/unit/application/services/job-transfer-storage.test.ts] - N509[apps/server/src/tests/unit/config/database.test.ts] - N510[apps/server/src/tests/unit/db/connection.test.ts] - N511[apps/server/src/tests/unit/domain/media/schemas.test.ts] - N512[apps/server/src/tests/unit/domain/media/utils/hash-utils.test.ts] - N513[apps/server/src/tests/unit/domain/media/utils/metadata-utils.test.ts] - N514[npm:@solid-imager/core/domain/media/utils/metadata-utils] - N515[apps/server/src/tests/unit/domain/search-mode-transition.test.ts] - N516[npm:@solid-imager/core/domain/search/logic] - N517[apps/server/src/tests/unit/infrastructure/api-clients/ai-api.test.ts] - N518[apps/server/src/tests/unit/infrastructure/api-clients/downloads-api.test.ts] - N519[npm:~/infrastructure/api-clients/downloads-api] - N520[apps/server/src/tests/unit/infrastructure/api-clients/sources-api-ext.test.ts] - N521[apps/server/src/tests/unit/infrastructure/file-system/node-file-system.test.ts] - N522[apps/server/src/tests/unit/infrastructure/jobs/download-jobs.test.ts] - N523[apps/server/src/tests/unit/infrastructure/jobs/download-rate-limiter.test.ts] - N524[apps/server/src/tests/unit/infrastructure/jobs/job-worker.test.ts] - N525[apps/server/src/tests/unit/infrastructure/jobs/ccip-jobs.test.ts] - N526[apps/server/src/tests/unit/infrastructure/jobs/tagging-jobs.test.ts] - N527[apps/server/src/tests/unit/infrastructure/storage/server-media-storage.test.ts] - N528[apps/server/src/tests/unit/infrastructure/events/realtime-event-bus.test.ts] - N529[apps/server/src/tests/unit/infrastructure/api/rpc-response-headers.test.ts] - N530[apps/server/src/tests/unit/media/copy-media-job.test.ts] - N531[apps/server/src/tests/unit/security/file-validation.test.ts] - N532[apps/server/src/tests/unit/server-config-service.test.ts] - N533[npm:~/infrastructure/services/server-config-service] - N534[apps/server/src/routeTree.gen.ts] - N535[apps/tauri/src/api/entities-api.ts] - N536[npm:~/orpc-client] - N537[apps/tauri/src/api/media-api.ts] - N538[apps/tauri/src/api/sources-api.ts] - N539[apps/tauri/node_modules/zod/index.d.cts] - N540[apps/tauri/src/main.tsx] - N541[apps/tauri/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] - N542[apps/tauri/node_modules/solid-js/types/index.d.ts] - N543[apps/tauri/node_modules/solid-js/web/types/index.d.ts] - N544[apps/tauri/src/index.css] - N545[apps/tauri/src/collections/index.ts] - N546[apps/tauri/src/router.tsx] - N547[apps/tauri/src/collections/authors-collection.ts] - N548[apps/tauri/node_modules/@tanstack/db/dist/cjs/index.cjs] - N549[apps/tauri/node_modules/@tanstack/query-db-collection/dist/cjs/index.cjs] - N550[apps/tauri/node_modules/@tanstack/tauri-db-sqlite-persistence/dist/cjs/index.cjs] - N551[npm:~/infrastructure/db/persistence] - N552[npm:~/router] - N553[apps/tauri/src/collections/query-keys.ts] - N554[apps/tauri/src/collections/characters-collection.ts] - N555[apps/tauri/src/collections/ips-collection.ts] - N556[apps/tauri/src/collections/projects-collection.ts] - N557[apps/tauri/src/collections/sources-collection.ts] - N558[apps/tauri/src/collections/tags-collection.ts] - N559[apps/tauri/src/components/imports/import-review-modal.tsx] - N560[npm:@solid-imager/ui/import-review-modal] - N561[apps/tauri/src/components/imports/pending-downloads-indicator.tsx] - N562[apps/tauri/src/components/media/ai-tagging-modal.tsx] - N563[apps/tauri/src/components/media/character-crop-modal.tsx] - N564[apps/tauri/src/components/media/media-grid-item.tsx] - N565[apps/tauri/src/components/media/media-sidebar/media-sidebar-content.tsx] - N566[npm:@solid-imager/ui/media-sidebar-content] - N567[apps/tauri/src/components/media/media-viewer.tsx] - N568[apps/tauri/src/components/media/move-copy-media-dialog.tsx] - N569[apps/tauri/src/components/media/thumbnail-image.tsx] - N570[apps/tauri/src/components/nav.tsx] - N571[apps/tauri/src/components/upload-media-modal/upload-media-modal-content.tsx] - N572[npm:@solid-imager/ui/upload-media-modal-content] - N573[apps/tauri/src/infrastructure/api-clients/ai-api.ts] - N574[apps/tauri/src/infrastructure/api-clients/characters-api.ts] - N575[apps/tauri/src/infrastructure/api-clients/imports-api.ts] - N576[apps/tauri/src/infrastructure/api-clients/ips-api.ts] - N577[apps/tauri/src/infrastructure/api-clients/projects-api.ts] - N578[apps/tauri/src/infrastructure/api-clients/search-api.ts] - N579[apps/tauri/src/infrastructure/api-clients/thumbnails-api.ts] - N580[apps/tauri/src/infrastructure/api/clients/preset-client.ts] - N581[apps/tauri/src/infrastructure/db/persistence.ts] - N582[apps/tauri/node_modules/@tauri-apps/plugin-sql/dist-js/index.cjs] - N583[apps/tauri/src/infrastructure/media/thumbnail-runtime.ts] - N584[npm:~/infrastructure/tauri-fetch-helpers] - N585[apps/tauri/src/infrastructure/tauri-fetch-helpers.ts] - N586[apps/tauri/node_modules/@tauri-apps/plugin-http/dist-js/index.cjs] - N587[apps/tauri/src/infrastructure/api-base.ts] - N588[apps/tauri/src/orpc-client.ts] - N589[apps/tauri/node_modules/@solid-imager/client/src/index.ts] - N590[apps/tauri/src/queries/index.ts] - N591[apps/tauri/node_modules/@orpc/solid-query/dist/index.d.mts] - N592[apps/tauri/src/routes/$.tsx] - N593[apps/tauri/src/routes/__root.tsx] - N594[npm:~/components/nav] - N595[apps/tauri/src/routes/about.tsx] - N596[apps/tauri/src/routes/config.tsx] - N597[apps/tauri/node_modules/@tanstack/solid-query/build/index.cjs] - N598[npm:~/queries] - N599[apps/tauri/src/routes/index.tsx] - N600[apps/tauri/src/routes/search.tsx] - N601[npm:@solid-imager/ui/hooks/use-search-page] - N602[npm:@solid-imager/ui/screens/search-screen] - N603[npm:~/components/media/media-grid-item] - N604[npm:~/hooks/use-current-search-persistence] - N605[apps/tauri/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] - N606[npm:@solid-imager/ui/hooks/use-source-root-path] - N607[npm:~/components/media/media-sidebar] - N608[npm:~/components/media/media-viewer] - N609[apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx] - N610[npm:~/components/media/move-copy-media-dialog] - N611[apps/tauri/src/routes/sources/$mediaSourceId/index.tsx] - N612[apps/tauri/src/routes/sources/index.tsx] - N613[apps/tauri/node_modules/@tanstack/solid-db/dist/esm/index.js] - N614[npm:~/collections] - N615[npm:~/collections/query-keys] - N616[apps/tauri/src/routes/v2/$.tsx] - N617[apps/tauri/src/routeTree.gen.ts] - N618[apps/tauri/src/routes/manager.tsx] - N619[apps/tauri/src/routes/v2.tsx] - N620[apps/xtracter/src/api.ts] - N621[apps/xtracter/node_modules/@solid-imager/client/src/index.ts] - N622[apps/xtracter/src/background/index.ts] - N623[npm:@core/domain/media/utils/filename-utils] - N624[npm:@core/domain/sources/schemas] - N625[npm:@ext/api] - N626[apps/xtracter/src/content/danbooru.ts] - N627[npm:@ext/schema] - N628[apps/xtracter/src/utils/dom-utils.ts] - N629[apps/xtracter/src/content/index.ts] - N630[apps/xtracter/src/content/fanbox.ts] - N631[apps/xtracter/src/content/twitter.ts] - N632[apps/xtracter/src/content/twitter.test.ts] - N633[node_modules/vitest/dist/index.js] - N634[apps/xtracter/src/popup/index.html] - N635[url:en] - N636[url:index.tsx] - N637[apps/xtracter/src/popup/index.tsx] - N638[apps/xtracter/node_modules/solid-js/types/index.d.ts] - N639[apps/xtracter/node_modules/solid-js/web/types/index.d.ts] - N640[apps/xtracter/src/schema.ts] - N641[apps/xtracter/node_modules/zod/index.d.cts] - N642[packages/application/src/ports/media-service.ts] - N643[packages/application/src/ports/media-processing-service.ts] - N644[packages/application/src/ports/search-service.ts] - N645[packages/application/src/services/ip-service.ts] - N646[packages/application/src/ports/ip-service.ts] - N647[packages/application/src/services/media-processing-service.ts] - N648[packages/application/src/services/media-query-service.ts] - N649[packages/application/node_modules/@solid-imager/core/src/index.ts] - N650[packages/application/src/services/media-service.ts] - N651[packages/application/src/services/media-transfer-service.ts] - N652[packages/application/src/services/media-upload-service.ts] - N653[packages/application/src/services/tagging-service.ts] - N654[npm:@solid-imager/core/domain/tagging/constants] - N655[packages/application/src/services/user-service.ts] - N656[packages/application/src/utils/hash-utils.ts] - N657[packages/client/src/create-client.ts] - N658[packages/client/node_modules/@orpc/client/dist/index.d.mts] - N659[npm:@orpc/client/fetch] - N660[packages/client/node_modules/@orpc/contract/dist/index.d.mts] - N661[packages/client/src/api-error.ts] - N662[packages/client/src/api-error.test.ts] - N663[packages/client/node_modules/vitest/dist/index.js] - N664[packages/client/src/create-client.test.ts] - N665[packages/core/src/domain/authors/schemas.ts] - N666[packages/core/node_modules/zod/index.d.cts] - N667[packages/core/src/domain/media/schemas.ts] - N668[packages/core/src/domain/categories/schemas.ts] - N669[packages/core/src/domain/characters/schemas.ts] - N670[packages/core/src/domain/collections/schemas.ts] - N671[packages/core/src/domain/config/config-schema.ts] - N672[packages/core/src/domain/contract/ai.contract.ts] - N673[packages/core/node_modules/@orpc/contract/dist/index.d.mts] - N674[packages/core/src/domain/contract/authors.contract.ts] - N675[packages/core/src/domain/contract/categories.contract.ts] - N676[packages/core/src/domain/contract/characters.contract.ts] - N677[packages/core/src/domain/contract/config.contract.ts] - N678[packages/core/src/domain/contract/directories.contract.ts] - N679[packages/core/src/domain/contract/downloads.contract.ts] - N680[packages/core/src/domain/contract/imports.contract.ts] - N681[packages/core/src/domain/contract/index.ts] - N682[packages/core/src/domain/contract/ips.contract.ts] - N683[packages/core/src/domain/contract/jobs.contract.ts] - N684[packages/core/src/domain/contract/media.contract.ts] - N685[packages/core/src/domain/contract/presets.contract.ts] - N686[packages/core/src/domain/contract/projects.contract.ts] - N687[packages/core/src/domain/contract/sources.contract.ts] - N688[packages/core/src/domain/contract/tags.contract.ts] - N689[packages/core/src/domain/contract/thumbnails.contract.ts] - N690[packages/core/src/domain/contract/utils.contract.ts] - N691[packages/core/src/domain/ips/schemas.ts] - N692[packages/core/src/domain/jobs/schemas.ts] - N693[packages/core/src/domain/sources/events.ts] - N694[packages/core/src/domain/contract/presets-client.ts] - N695[packages/core/src/domain/events/media-source-events.ts] - N696[packages/core/src/domain/media/upload-schemas.ts] - N697[packages/core/src/domain/media/utils/filename-utils.ts] - N698[packages/core/src/domain/media/utils/metadata-utils.ts] - N699[npm:@/domain/media/schemas] - N700[packages/core/src/domain/projects/schemas.ts] - N701[packages/core/src/domain/repositories/author-repository.ts] - N702[npm:@/domain/interfaces/transaction-manager] - N703[packages/core/src/domain/repositories/authors-repository.ts] - N704[npm:@/domain/authors/schemas] - N705[packages/core/src/domain/repositories/category-repository.ts] - N706[npm:@/domain/categories/schemas] - N707[packages/core/src/domain/repositories/ip-repository.ts] - N708[npm:@/domain/ips/schemas] - N709[packages/core/src/domain/repositories/media-repository.ts] - N710[packages/core/src/domain/repositories/project-repository.ts] - N711[packages/core/src/domain/repositories/source-repository.ts] - N712[packages/core/src/domain/repositories/tag-repository.ts] - N713[npm:@/domain/tags/schemas] - N714[packages/core/src/domain/repositories/user-repository.ts] - N715[npm:@/domain/users/schemas] - N716[packages/core/src/domain/search/schema.ts] - N717[packages/core/src/domain/services/storage-service.ts] - N718[npm:@/domain/media/upload-schemas] - N719[packages/core/src/domain/shared/schemas.ts] - N720[packages/core/src/domain/thumbnails/schemas.ts] - N721[packages/core/src/domain/sources/schemas.ts] - N722[packages/core/src/domain/sources/store.ts] - N723[packages/core/node_modules/solid-js/store/types/index.d.ts] - N724[packages/core/src/domain/tagging/schemas.ts] - N725[packages/core/src/domain/tags/extractor.ts] - N726[packages/core/src/utils/type-guards.ts] - N727[packages/core/src/domain/tags/schemas.ts] - N728[packages/core/src/domain/users/schemas.ts] - N729[packages/core/src/interfaces/config-service.ts] - N730[npm:@/domain/config/config-schema] - N731[packages/core/src/interfaces/media-storage.ts] - N732[packages/core/src/utils/deep-equal.ts] - N733[packages/db/src/repositories/author-repository.ts] - N734[packages/db/src/repositories/authors-repository.ts] - N735[packages/db/src/repositories/job-repository.ts] - N736[packages/db/src/repositories/media-repository-utils.ts] - N737[packages/db/src/repositories/project-repository.ts] - N738[packages/db/src/repositories/job-repository.test.ts] - N739[packages/db/node_modules/vitest/dist/index.js] - N740[packages/db/src/types.ts] - N741[packages/db/src/schema.ts] - N742[packages/db/node_modules/drizzle-orm/index.d.ts] - N743[packages/db/node_modules/drizzle-orm/node-postgres/index.d.ts] - N744[packages/db/node_modules/drizzle-orm/pglite/index.d.ts] - N745[packages/ui/src/ai-tagging-modal.tsx] - N746[packages/ui/node_modules/solid-js/types/index.d.ts] - N747[packages/ui/src/badge.tsx] - N748[packages/ui/src/association-manager.tsx] - N749[packages/ui/src/button.tsx] - N750[packages/ui/node_modules/class-variance-authority/dist/index.d.ts] - N751[packages/ui/src/utils/cn.ts] - N752[packages/ui/src/card.tsx] - N753[packages/ui/src/character-crop-modal.tsx] - N754[packages/ui/src/checkbox.tsx] - N755[packages/ui/src/clipboard-copy.tsx] - N756[packages/ui/src/toast.tsx] - N757[packages/ui/src/collapsible.tsx] - N758[packages/ui/node_modules/@kobalte/core/dist/index.d.ts] - N759[packages/ui/src/combobox.tsx] - N760[npm:@kobalte/core/combobox] - N761[npm:@kobalte/core/polymorphic] - N762[packages/ui/node_modules/@tanstack/solid-virtual/dist/cjs/index.cjs] - N763[packages/ui/src/command.tsx] - N764[npm:@kobalte/core/dialog] - N765[packages/ui/node_modules/cmdk-solid/dist/index.cjs] - N766[packages/ui/src/counter.tsx] - N767[packages/ui/src/dummy.test.ts] - N768[packages/ui/node_modules/vitest/dist/index.js] - N769[packages/ui/src/hooks/use-manager-page.ts] - N770[packages/ui/src/hooks/use-search-page.ts] - N771[packages/ui/src/hooks/use-source-media-page.test.ts] - N772[packages/ui/src/hooks/restore-import.ts] - N773[packages/ui/src/hooks/use-source-media-page.ts] - N774[packages/ui/src/hooks/use-source-root-path.test.ts] - N775[packages/ui/src/hooks/use-source-root-path.ts] - N776[packages/ui/node_modules/@tanstack/solid-query/build/index.cjs] - N777[packages/ui/src/hooks/use-batch-job-events.test.ts] - N778[packages/ui/src/hooks/use-current-search-persistence.test.ts] - N779[packages/ui/src/hooks/scroll-container.ts] - N780[packages/ui/node_modules/solid-js/web/types/index.d.ts] - N781[packages/ui/src/hooks/use-job-events.ts] - N782[packages/ui/src/event-stream.ts] - N783[packages/ui/src/import-inbox-helpers.ts] - N784[packages/ui/src/input.tsx] - N785[packages/ui/src/label.tsx] - N786[packages/ui/src/layouts/app-shell.tsx] - N787[packages/ui/src/media-card-item.tsx] - N788[packages/ui/src/media-grid-item.tsx] - N789[packages/ui/src/media-list-actions.tsx] - N790[packages/ui/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] - N791[packages/ui/src/media-sidebar-content.tsx] - N792[packages/ui/src/media-sidebar.tsx] - N793[packages/ui/src/move-copy-media-dialog.tsx] - N794[packages/ui/src/pagination-controls.tsx] - N795[packages/ui/src/pending-downloads-indicator.tsx] - N796[packages/ui/src/import-review-modal.tsx] - N797[packages/ui/src/pending-downloads-indicator.types.ts] - N798[packages/ui/src/pending-downloads-indicator-core.tsx] - N799[packages/ui/src/popover.tsx] - N800[npm:@kobalte/core/popover] - N801[packages/ui/src/preset-client.ts] - N802[packages/ui/src/pro-search-builder.tsx] - N803[packages/ui/src/pro-search-dialog.tsx] - N804[packages/ui/src/query-options/authors-query.ts] - N805[packages/ui/src/query-options/characters-query.ts] - N806[packages/ui/src/query-options/config-query.ts] - N807[packages/ui/src/query-options/ips-query.ts] - N808[packages/ui/src/query-options/media-query.ts] - N809[npm:@solid-imager/core/domain/shared/schemas] - N810[packages/ui/src/query-options/projects-query.ts] - N811[packages/ui/src/query-options/sources-query.ts] - N812[packages/ui/src/query-options/tags-query.ts] - N813[packages/ui/src/query-options/prefetch.ts] - N814[packages/ui/src/query-options/query-client.test.ts] - N815[packages/ui/src/query-options/query-client.ts] - N816[packages/ui/src/screens/config-screen.tsx] - N817[packages/ui/node_modules/@tanstack/solid-form/dist/cjs/index.cjs] - N818[packages/ui/node_modules/zod/index.d.cts] - N819[packages/ui/src/screens/manager-screen.tsx] - N820[packages/ui/src/screens/not-found-screen.tsx] - N821[packages/ui/src/screens/search-screen.tsx] - N822[packages/ui/src/async-state.tsx] - N823[packages/ui/src/mobile-search-filter-dialog.tsx] - N824[packages/ui/src/search-control-panel.tsx] - N825[packages/ui/src/skeleton.tsx] - N826[packages/ui/src/source-media-grid.tsx] - N827[packages/ui/src/screens/search-screen.types.ts] - N828[packages/ui/src/screens/source-media-screen.tsx] - N829[packages/ui/src/screens/config-state-screen.types.ts] - N830[packages/ui/src/query-state.ts] - N831[packages/ui/src/screens/design-concept-screen.tsx] - N832[npm:lucide-solid/icons/arrow-down-up] - N833[npm:lucide-solid/icons/arrow-left] - N834[npm:lucide-solid/icons/ban] - N835[npm:lucide-solid/icons/bot] - N836[npm:lucide-solid/icons/briefcase-business] - N837[npm:lucide-solid/icons/chevron-down] - N838[npm:lucide-solid/icons/chevron-left] - N839[npm:lucide-solid/icons/chevron-right] - N840[npm:lucide-solid/icons/circle-alert] - N841[npm:lucide-solid/icons/circle-check] - N842[npm:lucide-solid/icons/clock-3] - N843[npm:lucide-solid/icons/cloud-download] - N844[npm:lucide-solid/icons/database] - N845[npm:lucide-solid/icons/download] - N846[npm:lucide-solid/icons/external-link] - N847[npm:lucide-solid/icons/filter] - N848[npm:lucide-solid/icons/folder] - N849[npm:lucide-solid/icons/grid-3-x-3] - N850[npm:lucide-solid/icons/hard-drive] - N851[npm:lucide-solid/icons/image] - N852[npm:lucide-solid/icons/inbox] - N853[npm:lucide-solid/icons/library] - N854[npm:lucide-solid/icons/list] - N855[npm:lucide-solid/icons/logs] - N856[npm:lucide-solid/icons/panel-left-close] - N857[npm:lucide-solid/icons/panel-left-open] - N858[npm:lucide-solid/icons/panels-top-left] - N859[npm:lucide-solid/icons/plus] - N860[npm:lucide-solid/icons/refresh-cw] - N861[npm:lucide-solid/icons/rotate-ccw] - N862[npm:lucide-solid/icons/search] - N863[npm:lucide-solid/icons/settings] - N864[npm:lucide-solid/icons/share-2] - N865[npm:lucide-solid/icons/trash-2] - N866[npm:lucide-solid/icons/x] - N867[packages/ui/src/screens/legacy-config-state-screen.tsx] - N868[packages/ui/src/screens/legacy-media-detail-screen.tsx] - N869[packages/ui/src/legacy-media-detail-skeleton.tsx] - N870[packages/ui/src/screens/media-detail-screen.types.ts] - N871[packages/ui/src/screens/media-detail-screen-core.tsx] - N872[packages/ui/src/screens/source-media-screen.types.ts] - N873[packages/ui/src/screens/v2-config-screen.tsx] - N874[packages/ui/src/screens/v2-config-state-screen.tsx] - N875[packages/ui/src/v2/management-layout.tsx] - N876[packages/ui/src/screens/v2-manager-screen.tsx] - N877[packages/ui/src/screens/v2-manager/batch-tools.tsx] - N878[packages/ui/src/screens/v2-manager/job-status.tsx] - N879[packages/ui/src/screens/v2-manager/source-select.tsx] - N880[packages/ui/src/screens/v2-manager/data-transfer.tsx] - N881[npm:lucide-solid/icons/upload] - N882[packages/ui/src/screens/v2-manager/dialogs.tsx] - N883[packages/ui/src/screens/v2-manager/duplicates.tsx] - N884[packages/ui/src/screens/v2-manager/entity-panel.tsx] - N885[npm:lucide-solid/icons/pencil] - N886[packages/ui/src/progress.tsx] - N887[packages/ui/src/screens/v2-manager/navigation.tsx] - N888[npm:lucide-solid/icons/copy-check] - N889[packages/ui/src/screens/v2-manager/thumbnail.tsx] - N890[packages/ui/src/screens/v2-manager/types.ts] - N891[packages/ui/src/screens/v2-manager/utils.test.ts] - N892[packages/ui/src/screens/v2-manager/utils.ts] - N893[packages/ui/src/screens/v2-media-detail-screen.tsx] - N894[packages/ui/src/v2-media-detail-skeleton.tsx] - N895[packages/ui/src/screens/v2-search-screen.tsx] - N896[packages/ui/src/screens/v2-source-media-screen.tsx] - N897[npm:@solid-imager/core/domain/search/schema] - N898[packages/ui/node_modules/solid-js/store/types/index.d.ts] - N899[packages/ui/src/preset-manager.tsx] - N900[packages/ui/src/search-filters.tsx] - N901[packages/ui/src/select.tsx] - N902[packages/ui/src/sort-controls.tsx] - N903[packages/ui/src/source-delete-modal.tsx] - N904[packages/ui/src/source-media-page.tsx] - N905[packages/ui/src/stores/search-store.ts] - N906[packages/ui/src/stores/search-store.test.ts] - N907[packages/ui/src/switch.tsx] - N908[packages/ui/src/tabs.tsx] - N909[packages/ui/src/textarea.tsx] - N910[packages/ui/src/thumbnail-image.tsx] - N911[packages/ui/src/thumbnail-source.ts] - N912[packages/ui/node_modules/clsx/dist/clsx.js] - N913[packages/ui/node_modules/tailwind-merge/dist/types.d.ts] - N914[packages/ui/src/utils/debounce.ts] - N915[packages/ui/src/event-stream.test.ts] - N916[packages/ui/src/form-message.tsx] - N917[packages/ui/src/form-schemas.test.ts] - N918[packages/ui/src/form-schemas.ts] - N919[packages/ui/src/oppai-oracle-modal.tsx] - N920[packages/ui/src/query-state.test.ts] - N921[packages/ui/src/router-status.tsx] - N922[packages/ui/src/screen-skeleton.tsx] - N923[packages/ui/src/text-field.tsx] - N924[npm:@kobalte/core/text-field] - N925[packages/ui/src/import-review-modal.types.ts] - N926[packages/ui/src/legacy-import-review-modal.tsx] - N927[packages/ui/src/legacy-upload-media-modal.tsx] - N928[packages/ui/src/media-preview-selection.test.ts] - N929[packages/ui/src/media-preview-selection.ts] - N930[packages/ui/src/v2/collection-inspector.tsx] - N931[packages/ui/src/v2/icons.tsx] - N932[packages/ui/src/v2/search-composer-utils.ts] - N933[packages/ui/src/v2/search-composer.test.ts] - N934[packages/ui/src/v2/search-composer.tsx] - N935[packages/ui/src/v2/search-toolbar.tsx] - N936[packages/ui/src/thumbnail-source.test.ts] - N937[packages/ui/src/v2-import-review-modal.tsx] - N938[packages/ui/src/v2-media-grid-item.tsx] - N939[packages/ui/src/v2-pending-downloads-indicator.tsx] - N940[packages/ui/src/v2-upload-media-modal-content.tsx] - N941[packages/ui/src/upload-media-modal-content.types.ts] - N942[packages/ui/src/v2-upload-media-modal.tsx] + N18[apps/tauri/src/api/entities-api.ts] + N19[npm:~/orpc-client] + N20[apps/tauri/src/api/media-api.ts] + N21[apps/tauri/src/api/sources-api.ts] + N22[npm:@solid-imager/core/domain/sources/schemas] + N23[apps/tauri/node_modules/zod/index.d.cts] + N24[apps/tauri/src/main.tsx] + N25[npm:@solid-imager/ui/layouts/app-shell] + N26[npm:@solid-imager/ui/router-status] + N27[apps/tauri/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] + N28[apps/tauri/node_modules/solid-js/types/index.d.ts] + N29[apps/tauri/node_modules/solid-js/web/types/index.d.ts] + N30[apps/tauri/src/index.css] + N31[apps/tauri/src/collections/index.ts] + N32[apps/tauri/src/router.tsx] + N33[apps/tauri/src/collections/authors-collection.ts] + N34[apps/tauri/node_modules/@tanstack/db/dist/cjs/index.cjs] + N35[apps/tauri/node_modules/@tanstack/query-db-collection/dist/cjs/index.cjs] + N36[apps/tauri/node_modules/@tanstack/tauri-db-sqlite-persistence/dist/cjs/index.cjs] + N37[npm:~/infrastructure/db/persistence] + N38[npm:~/router] + N39[apps/tauri/src/collections/query-keys.ts] + N40[apps/tauri/src/collections/characters-collection.ts] + N41[apps/tauri/src/collections/ips-collection.ts] + N42[apps/tauri/src/collections/projects-collection.ts] + N43[apps/tauri/src/collections/sources-collection.ts] + N44[apps/tauri/src/collections/tags-collection.ts] + N45[apps/tauri/src/components/imports/import-review-modal.tsx] + N46[npm:@solid-imager/ui/import-review-modal] + N47[apps/tauri/src/components/imports/pending-downloads-indicator.tsx] + N48[npm:@solid-imager/ui/event-stream] + N49[npm:@solid-imager/ui/pending-downloads-indicator] + N50[apps/tauri/src/components/media/ai-tagging-modal.tsx] + N51[npm:@solid-imager/ui/ai-tagging-modal] + N52[npm:~/infrastructure/api-clients/orpc-client] + N53[apps/tauri/src/components/media/character-crop-modal.tsx] + N54[npm:@solid-imager/core/domain/media/schemas] + N55[npm:@solid-imager/ui/character-crop-modal] + N56[apps/tauri/src/components/media/media-grid-item.tsx] + N57[apps/tauri/src/components/media/media-sidebar/media-sidebar-content.tsx] + N58[npm:@solid-imager/ui/media-sidebar-content] + N59[npm:@solid-imager/ui/stores/search-store] + N60[npm:~/hooks/use-batch-job-events] + N61[apps/tauri/src/components/media/media-viewer.tsx] + N62[apps/tauri/src/components/media/move-copy-media-dialog.tsx] + N63[npm:@solid-imager/ui/move-copy-media-dialog] + N64[npm:~/infrastructure/api-clients/sources-api] + N65[apps/tauri/src/components/media/thumbnail-image.tsx] + N66[apps/tauri/src/components/nav.tsx] + N67[npm:@solid-imager/ui/layouts/app-nav] + N68[apps/tauri/src/components/upload-media-modal/upload-media-modal-content.tsx] + N69[npm:@solid-imager/ui/upload-media-modal-content] + N70[url:] + N71[apps/tauri/src/infrastructure/api-clients/ai-api.ts] + N72[apps/tauri/src/infrastructure/api-clients/characters-api.ts] + N73[apps/tauri/src/infrastructure/api-clients/imports-api.ts] + N74[apps/tauri/src/infrastructure/api-clients/ips-api.ts] + N75[apps/tauri/src/infrastructure/api-clients/projects-api.ts] + N76[apps/tauri/src/infrastructure/api-clients/search-api.ts] + N77[apps/tauri/src/infrastructure/api-clients/thumbnails-api.ts] + N78[apps/tauri/src/infrastructure/api/clients/preset-client.ts] + N79[npm:@solid-imager/core/domain/contract/presets-client] + N80[apps/tauri/src/infrastructure/api/clients/search-history-client.ts] + N81[npm:@solid-imager/core/domain/contract/search-snapshots-client] + N82[apps/tauri/src/infrastructure/db/persistence.ts] + N83[apps/tauri/node_modules/@tauri-apps/plugin-sql/dist-js/index.cjs] + N84[apps/tauri/src/infrastructure/media/thumbnail-runtime.ts] + N85[npm:~/infrastructure/tauri-fetch-helpers] + N86[apps/tauri/src/infrastructure/tauri-fetch-helpers.ts] + N87[apps/tauri/node_modules/@tauri-apps/plugin-http/dist-js/index.cjs] + N88[apps/tauri/src/infrastructure/api-base.ts] + N89[apps/tauri/src/orpc-client.ts] + N90[apps/tauri/node_modules/@solid-imager/client/src/index.ts] + N91[npm:@solid-imager/core/domain/contract] + N92[apps/tauri/src/queries/index.ts] + N93[apps/tauri/node_modules/@orpc/solid-query/dist/index.d.mts] + N94[npm:@solid-imager/ui/query-options] + N95[apps/tauri/src/routes/$.tsx] + N96[npm:@solid-imager/ui/screens/not-found-screen] + N97[apps/tauri/src/routes/__root.tsx] + N98[npm:@solid-imager/ui/toast] + N99[npm:~/components/nav] + N100[apps/tauri/src/routes/about.tsx] + N101[npm:@solid-imager/ui/badge] + N102[apps/tauri/src/routes/config.tsx] + N103[npm:@solid-imager/ui/query-state] + N104[npm:@solid-imager/ui/screens/legacy-config-state-screen] + N105[apps/tauri/node_modules/@tanstack/solid-query/build/index.cjs] + N106[npm:~/queries] + N107[apps/tauri/src/routes/index.tsx] + N108[npm:@solid-imager/ui/button] + N109[apps/tauri/src/routes/search.tsx] + N110[npm:@solid-imager/ui/hooks/use-current-search-persistence] + N111[npm:@solid-imager/ui/hooks/use-search-history-persistence] + N112[npm:@solid-imager/ui/hooks/use-search-page] + N113[npm:@solid-imager/ui/preset-client] + N114[npm:@solid-imager/ui/screens/search-screen] + N115[npm:@solid-imager/ui/search-history-client] + N116[npm:@solid-imager/ui/search-history-route] + N117[npm:~/components/media/media-grid-item] + N118[npm:~/hooks/use-media-source-events] + N119[npm:~/infrastructure/api/clients/preset-client] + N120[npm:~/infrastructure/api/clients/search-history-client] + N121[apps/tauri/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] + N122[npm:@solid-imager/ui/hooks/use-source-root-path] + N123[npm:@solid-imager/ui/screens/legacy-media-detail-screen] + N124[npm:~/components/media/media-sidebar] + N125[npm:~/components/media/media-viewer] + N126[apps/tauri/src/routes/sources/$mediaSourceId/components/source-media-page.tsx] + N127[npm:@solid-imager/ui/screens/source-media-screen] + N128[npm:@solid-imager/ui/source-media-page] + N129[npm:~/components/media/move-copy-media-dialog] + N130[npm:~/components/upload-media-modal] + N131[apps/tauri/src/routes/sources/$mediaSourceId/index.tsx] + N132[apps/tauri/src/routes/sources/index.tsx] + N133[npm:@solid-imager/ui/hooks/use-sources-events] + N134[npm:@solid-imager/ui/hooks/use-sources-page] + N135[npm:@solid-imager/ui/legacy-source-form-modal] + N136[npm:@solid-imager/ui/screens/sources-screen] + N137[npm:@solid-imager/ui/source-card] + N138[npm:@solid-imager/ui/source-delete-modal] + N139[apps/tauri/node_modules/@tanstack/solid-db/dist/esm/index.js] + N140[npm:~/collections] + N141[npm:~/collections/query-keys] + N142[apps/tauri/src/routes/v2/$.tsx] + N143[apps/tauri/src/routes/jobs.tsx] + N144[apps/tauri/src/routeTree.gen.ts] + N145[apps/tauri/src/routes/manager.tsx] + N146[apps/tauri/src/routes/v2.tsx] + N147[apps/xtracter/src/api.ts] + N148[apps/xtracter/node_modules/@solid-imager/client/src/index.ts] + N149[apps/xtracter/src/background/index.ts] + N150[npm:@core/domain/media/utils/filename-utils] + N151[npm:@core/domain/sources/schemas] + N152[npm:@ext/api] + N153[apps/xtracter/src/content/danbooru.ts] + N154[npm:@ext/schema] + N155[apps/xtracter/src/utils/dom-utils.ts] + N156[apps/xtracter/src/content/index.ts] + N157[apps/xtracter/src/content/fanbox.ts] + N158[apps/xtracter/src/content/twitter.ts] + N159[apps/xtracter/src/content/twitter.test.ts] + N160[node_modules/vitest/dist/index.js] + N161[apps/xtracter/src/popup/index.html] + N162[url:en] + N163[url:UTF-8] + N164[url:viewport] + N165[url:width=device-width, initial-scale=1.0] + N166[url:root] + N167[url:module] + N168[url:index.tsx] + N169[apps/xtracter/src/popup/index.tsx] + N170[apps/xtracter/node_modules/solid-js/types/index.d.ts] + N171[apps/xtracter/node_modules/solid-js/web/types/index.d.ts] + N172[apps/xtracter/src/schema.ts] + N173[apps/xtracter/node_modules/zod/index.d.cts] + N174[packages/application/src/ports/media-service.ts] + N175[npm:@solid-imager/core/domain/interfaces/transaction-manager] + N176[packages/application/src/ports/media-processing-service.ts] + N177[packages/application/src/ports/search-service.ts] + N178[packages/application/src/services/ip-service.ts] + N179[npm:@solid-imager/core/domain/ips/schemas] + N180[npm:@solid-imager/core/domain/repositories/ip-repository] + N181[packages/application/src/ports/ip-service.ts] + N182[packages/application/src/services/media-processing-service.ts] + N183[npm:@solid-imager/core/domain/characters/schemas] + N184[packages/application/src/services/media-query-service.ts] + N185[packages/application/node_modules/@solid-imager/core/src/index.ts] + N186[npm:@solid-imager/core/domain/errors] + N187[packages/application/src/services/media-service.ts] + N188[packages/application/src/services/media-transfer-service.ts] + N189[packages/application/src/services/media-upload-service.ts] + N190[packages/application/src/services/tagging-service.ts] + N191[npm:@solid-imager/core/domain/interfaces/ai-client] + N192[npm:@solid-imager/core/domain/repositories/character-repository] + N193[npm:@solid-imager/core/domain/repositories/media-repository] + N194[npm:@solid-imager/core/domain/repositories/source-repository] + N195[npm:@solid-imager/core/domain/repositories/tag-repository] + N196[npm:@solid-imager/core/domain/sources/events] + N197[npm:@solid-imager/core/domain/tagging/constants] + N198[packages/application/src/services/user-service.ts] + N199[npm:@solid-imager/core/domain/repositories/user-repository] + N200[packages/application/src/services/search-snapshot-service.ts] + N201[npm:node:crypto] + N202[npm:@solid-imager/core/domain/repositories/search-snapshot-repository] + N203[packages/application/src/utils/hash-utils.ts] + N204[packages/client/src/create-client.ts] + N205[packages/client/node_modules/@orpc/client/dist/index.d.mts] + N206[npm:@orpc/client/fetch] + N207[packages/client/node_modules/@orpc/contract/dist/index.d.mts] + N208[packages/client/src/api-error.ts] + N209[packages/client/src/api-error.test.ts] + N210[packages/client/node_modules/vitest/dist/index.js] + N211[packages/client/src/create-client.test.ts] + N212[packages/core/src/domain/authors/schemas.ts] + N213[packages/core/node_modules/zod/index.d.cts] + N214[packages/core/src/domain/media/schemas.ts] + N215[packages/core/src/domain/categories/schemas.ts] + N216[packages/core/src/domain/characters/schemas.ts] + N217[packages/core/src/domain/collections/schemas.ts] + N218[packages/core/src/domain/config/config-schema.ts] + N219[packages/core/src/domain/contract/ai.contract.ts] + N220[packages/core/node_modules/@orpc/contract/dist/index.d.mts] + N221[packages/core/src/domain/contract/authors.contract.ts] + N222[packages/core/src/domain/contract/categories.contract.ts] + N223[packages/core/src/domain/contract/characters.contract.ts] + N224[packages/core/src/domain/contract/config.contract.ts] + N225[packages/core/src/domain/contract/directories.contract.ts] + N226[packages/core/src/domain/contract/downloads.contract.ts] + N227[packages/core/src/domain/contract/imports.contract.ts] + N228[packages/core/src/domain/contract/index.ts] + N229[packages/core/src/domain/contract/ips.contract.ts] + N230[packages/core/src/domain/contract/jobs.contract.ts] + N231[packages/core/src/domain/contract/media.contract.ts] + N232[packages/core/src/domain/contract/presets.contract.ts] + N233[packages/core/src/domain/contract/projects.contract.ts] + N234[packages/core/src/domain/contract/search-snapshots.contract.ts] + N235[packages/core/src/domain/contract/sources.contract.ts] + N236[packages/core/src/domain/contract/tags.contract.ts] + N237[packages/core/src/domain/contract/thumbnails.contract.ts] + N238[packages/core/src/domain/contract/utils.contract.ts] + N239[packages/core/src/domain/ips/schemas.ts] + N240[packages/core/src/domain/jobs/schemas.ts] + N241[packages/core/src/domain/sources/events.ts] + N242[packages/core/src/domain/contract/presets-client.ts] + N243[packages/core/src/domain/contract/search-snapshots-client.ts] + N244[packages/core/src/domain/events/media-source-events.ts] + N245[packages/core/src/domain/media/upload-schemas.ts] + N246[packages/core/src/domain/media/utils/filename-utils.ts] + N247[packages/core/src/domain/media/utils/metadata-utils.ts] + N248[npm:@/domain/media/schemas] + N249[packages/core/src/domain/projects/schemas.ts] + N250[packages/core/src/domain/repositories/author-repository.ts] + N251[npm:@/domain/interfaces/transaction-manager] + N252[packages/core/src/domain/repositories/authors-repository.ts] + N253[npm:@/domain/authors/schemas] + N254[packages/core/src/domain/repositories/category-repository.ts] + N255[npm:@/domain/categories/schemas] + N256[packages/core/src/domain/repositories/ip-repository.ts] + N257[npm:@/domain/ips/schemas] + N258[packages/core/src/domain/repositories/media-repository.ts] + N259[packages/core/src/domain/repositories/project-repository.ts] + N260[packages/core/src/domain/repositories/source-repository.ts] + N261[packages/core/src/domain/repositories/tag-repository.ts] + N262[npm:@/domain/tags/schemas] + N263[packages/core/src/domain/repositories/user-repository.ts] + N264[npm:@/domain/users/schemas] + N265[packages/core/src/domain/search/schema.ts] + N266[packages/core/src/domain/search/history.ts] + N267[packages/core/src/domain/services/storage-service.ts] + N268[npm:@/domain/media/upload-schemas] + N269[packages/core/src/domain/shared/schemas.ts] + N270[packages/core/src/domain/thumbnails/schemas.ts] + N271[packages/core/src/domain/sources/schemas.ts] + N272[packages/core/src/domain/sources/store.ts] + N273[packages/core/node_modules/solid-js/store/types/index.d.ts] + N274[packages/core/src/domain/tagging/schemas.ts] + N275[packages/core/src/domain/tags/extractor.ts] + N276[packages/core/src/utils/type-guards.ts] + N277[packages/core/src/domain/tags/schemas.ts] + N278[packages/core/src/domain/users/schemas.ts] + N279[packages/core/src/interfaces/config-service.ts] + N280[npm:@/domain/config/config-schema] + N281[packages/core/src/interfaces/media-storage.ts] + N282[packages/core/src/utils/deep-equal.ts] + N283[packages/db/src/repositories/author-repository.ts] + N284[packages/db/src/repositories/authors-repository.ts] + N285[npm:@solid-imager/core/domain/authors/schemas] + N286[packages/db/src/repositories/job-repository.ts] + N287[npm:@solid-imager/core/domain/jobs/schemas] + N288[packages/db/src/repositories/media-repository-utils.ts] + N289[packages/db/src/repositories/project-repository.ts] + N290[packages/db/src/repositories/job-repository.test.ts] + N291[packages/db/node_modules/vitest/dist/index.js] + N292[packages/db/src/types.ts] + N293[packages/db/src/repositories/search-snapshot-repository.ts] + N294[packages/db/src/schema.ts] + N295[npm:@solid-imager/core/domain/repositories/job-repository] + N296[packages/db/node_modules/drizzle-orm/index.d.ts] + N297[packages/db/node_modules/drizzle-orm/node-postgres/index.d.ts] + N298[packages/db/node_modules/drizzle-orm/pglite/index.d.ts] + N299[apps/server/src/tests/api/categories/category-id-test.ts] + N300[apps/server/node_modules/vitest/dist/index.js] + N301[npm:~/infrastructure/db/schema] + N302[apps/server/src/tests/api/categories/index.test.ts] + N303[apps/server/src/tests/api/characters/character-id-test.ts] + N304[apps/server/src/tests/api/ips/ip-id-test.ts] + N305[apps/server/src/tests/api/media/add-media.test.ts] + N306[apps/server/node_modules/zod/index.d.cts] + N307[apps/server/src/tests/api/media/delete-media.test.ts] + N308[apps/server/src/tests/api/media/get-media.test.ts] + N309[apps/server/src/tests/api/media/list-media.test.ts] + N310[apps/server/src/tests/api/tags/index.test.ts] + N311[apps/server/src/tests/api/tags/tag-id-test.ts] + N312[apps/server/src/tests/e2e/app-nav.responsive.spec.ts] + N313[apps/server/node_modules/@playwright/test/index.d.ts] + N314[apps/server/src/tests/e2e/support/test.ts] + N315[apps/server/src/tests/e2e/loading-recovery.spec.ts] + N316[apps/server/src/tests/e2e/media-detail-manager-config.responsive.spec.ts] + N317[apps/server/src/tests/e2e/realtime-preservation.spec.ts] + N318[npm:node:fs/promises] + N319[apps/server/src/tests/e2e/route-reload.spec.ts] + N320[apps/server/src/tests/e2e/search-pro-dialog.responsive.spec.ts] + N321[apps/server/src/tests/e2e/search-realtime-preservation.responsive.spec.ts] + N322[apps/server/src/tests/e2e/search.responsive.spec.ts] + N323[apps/server/src/tests/e2e/support/fixture.ts] + N324[apps/server/src/tests/e2e/sources-source-media.responsive.spec.ts] + N325[apps/server/src/tests/e2e/ui-components.gallery.spec.ts] + N326[apps/server/src/tests/e2e/ui-gallery/index.html] + N327[url:ja] + N328[url:src.tsx] + N329[apps/server/src/tests/e2e/ui-gallery/src.tsx] + N330[apps/server/src/tests/e2e/ui-gallery/vite.config.ts] + N331[npm:node:url] + N332[apps/server/node_modules/@tailwindcss/vite/dist/index.d.mts] + N333[apps/server/node_modules/sharp/dist/index.cjs] + N334[apps/server/node_modules/vite/dist/node/index.js] + N335[apps/server/node_modules/vite-plugin-solid/dist/cjs/index.cjs] + N336[apps/server/src/tests/e2e/v2-routes.responsive.spec.ts] + N337[apps/server/src/tests/e2e/v2-scroll-restoration.spec.ts] + N338[apps/server/src/tests/integration/backup/backup-service.test.ts] + N339[apps/server/node_modules/drizzle-orm/index.d.ts] + N340[npm:~/infrastructure/db] + N341[apps/server/src/tests/integration/backup/performance.test.ts] + N342[apps/server/src/tests/integration/backup/zip-backup.test.ts] + N343[npm:node:os] + N344[apps/server/src/tests/integration/db/pglite-parity.test.ts] + N345[npm:~/config/database] + N346[apps/server/src/tests/integration/media/access-denied-integration.test.ts] + N347[npm:~/infrastructure/ai/rust-ai-client] + N348[npm:~/infrastructure/processing/image-processor] + N349[npm:~/infrastructure/repositories/author-repository] + N350[npm:~/infrastructure/repositories/character-repository] + N351[npm:~/infrastructure/repositories/ip-repository] + N352[npm:~/infrastructure/repositories/media-repository] + N353[npm:~/infrastructure/repositories/project-repository] + N354[npm:~/infrastructure/repositories/source-repository] + N355[npm:~/infrastructure/repositories/tag-repository] + N356[npm:~/infrastructure/service-registry] + N357[npm:~/infrastructure/services/media-service] + N358[npm:~/infrastructure/storage/server-media-storage] + N359[apps/server/src/tests/integration/media/add-media-integration.test.ts] + N360[npm:~/infrastructure/db/index] + N361[apps/server/src/tests/integration/media/copy-media-integration.test.ts] + N362[apps/server/src/tests/integration/media/delete-media-integration.test.ts] + N363[apps/server/src/tests/integration/media/get-media-details-integration.test.ts] + N364[apps/server/src/tests/integration/media/get-media-integration.test.ts] + N365[apps/server/src/tests/integration/media/list-media-integration.test.ts] + N366[apps/server/src/tests/integration/media/media-type-handling.test.ts] + N367[apps/server/src/tests/integration/media/register-media-integration.test.ts] + N368[apps/server/src/tests/integration/media/update-media-integration.test.ts] + N369[apps/server/src/tests/integration/queries/search.test.ts] + N370[apps/server/src/tests/integration/repository/author-dedupe.test.ts] + N371[apps/server/node_modules/drizzle-orm/pglite/migrator.d.ts] + N372[apps/server/src/tests/integration/repository/character-repository.test.ts] + N373[apps/server/src/tests/integration/security/backup-security.test.ts] + N374[npm:~/infrastructure/services/backup-service] + N375[apps/server/src/tests/integration/security/path-traversal.test.ts] + N376[apps/server/src/tests/integration/ai/postgres-ccip-vector-store.test.ts] + N377[npm:@solid-imager/application/ports/ccip-vector-store] + N378[apps/server/src/tests/monorepo-migration.test.ts] + N379[apps/server/src/tests/setup-integration.ts] + N380[apps/server/node_modules/dotenv/lib/main.d.ts] + N381[apps/server/src/tests/setup-unit.ts] + N382[apps/server/src/tests/setup.ts] + N383[apps/server/src/tests/unit/application/registry.test.ts] + N384[apps/server/src/tests/unit/application/services/backup-service.test.ts] + N385[apps/server/src/tests/unit/application/services/character-service.test.ts] + N386[apps/server/src/tests/unit/application/services/directory-sync-service.test.ts] + N387[apps/server/src/tests/unit/application/services/media-service.test.ts] + N388[npm:@solid-imager/application/services/media-query-service] + N389[npm:@solid-imager/application/services/media-transfer-service] + N390[npm:@solid-imager/application/services/media-upload-service] + N391[apps/server/node_modules/@solid-imager/core/src/index.ts] + N392[npm:@solid-imager/core/domain/repositories/author-repository] + N393[npm:@solid-imager/core/domain/repositories/project-repository] + N394[npm:@solid-imager/core/domain/services/image-processor] + N395[npm:~/infrastructure/db/transaction-manager] + N396[apps/server/src/tests/unit/application/services/ccip-vector-service.test.ts] + N397[npm:@solid-imager/application/services/ccip-vector-service] + N398[apps/server/src/tests/unit/application/services/maintenance-service.test.ts] + N399[apps/server/src/tests/unit/application/services/media-processing-service.test.ts] + N400[npm:~/infrastructure/services/media-processing-service] + N401[apps/server/src/tests/unit/application/services/tagging-service.test.ts] + N402[npm:@solid-imager/application/services/tagging-service] + N403[apps/server/src/tests/unit/application/services/job-dispatch-service.test.ts] + N404[apps/server/src/tests/unit/application/services/job-transfer-storage.test.ts] + N405[apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts] + N406[apps/server/src/tests/unit/config/database.test.ts] + N407[apps/server/src/tests/unit/db/connection.test.ts] + N408[apps/server/src/tests/unit/domain/media/schemas.test.ts] + N409[apps/server/src/tests/unit/domain/media/utils/hash-utils.test.ts] + N410[apps/server/node_modules/@solid-imager/application/src/index.ts] + N411[apps/server/src/tests/unit/domain/media/utils/metadata-utils.test.ts] + N412[npm:@solid-imager/core/domain/media/utils/metadata-utils] + N413[apps/server/src/tests/unit/domain/search-mode-transition.test.ts] + N414[npm:@solid-imager/core/domain/search/logic] + N415[apps/server/src/tests/unit/infrastructure/api-clients/ai-api.test.ts] + N416[npm:~/infrastructure/api-clients/ai-api] + N417[apps/server/src/tests/unit/infrastructure/api-clients/downloads-api.test.ts] + N418[npm:~/infrastructure/api-clients/downloads-api] + N419[apps/server/src/tests/unit/infrastructure/api-clients/sources-api-ext.test.ts] + N420[apps/server/src/tests/unit/infrastructure/file-system/node-file-system.test.ts] + N421[npm:~/infrastructure/file-system/node-file-system] + N422[apps/server/src/tests/unit/infrastructure/jobs/download-jobs.test.ts] + N423[npm:~/infrastructure/jobs/download-jobs] + N424[apps/server/src/tests/unit/infrastructure/jobs/download-rate-limiter.test.ts] + N425[apps/server/src/tests/unit/infrastructure/jobs/job-worker.test.ts] + N426[npm:@solid-imager/core/domain/config/config-schema] + N427[npm:~/domain/repositories/job-repository] + N428[npm:~/infrastructure/jobs/job-worker] + N429[apps/server/src/tests/unit/infrastructure/jobs/ccip-jobs.test.ts] + N430[apps/server/src/tests/unit/infrastructure/jobs/tagging-jobs.test.ts] + N431[apps/server/src/tests/unit/infrastructure/storage/server-media-storage.test.ts] + N432[apps/server/node_modules/fluent-ffmpeg/index.js] + N433[apps/server/src/tests/unit/infrastructure/events/realtime-event-bus.test.ts] + N434[npm:~/infrastructure/events/realtime-event-bus] + N435[apps/server/src/tests/unit/infrastructure/api/rpc-response-headers.test.ts] + N436[apps/server/node_modules/@orpc/server/dist/index.d.mts] + N437[npm:@orpc/server/fetch] + N438[npm:@orpc/server/plugins] + N439[npm:~/infrastructure/api/rpc-response-headers] + N440[apps/server/src/tests/unit/infrastructure/ai/inference-options.test.ts] + N441[npm:~/infrastructure/ai/inference-options] + N442[apps/server/src/tests/unit/media/copy-media-job.test.ts] + N443[npm:~/infrastructure/jobs/thumbnails] + N444[apps/server/src/tests/unit/security/file-validation.test.ts] + N445[apps/server/src/tests/unit/server-config-service.test.ts] + N446[npm:~/infrastructure/services/server-config-service] + N447[apps/server/src/infrastructure/ai/rust-ai-client.ts] + N448[apps/server/node_modules/@solid-imager/client/src/index.ts] + N449[apps/server/src/infrastructure/ai/inference-options.ts] + N450[apps/server/node_modules/dghs-imgutils-rs/index.js] + N451[apps/server/src/infrastructure/api-clients/ai-api.ts] + N452[npm:@solid-imager/core/domain/tagging/schemas] + N453[apps/server/src/infrastructure/api-clients/orpc-client.ts] + N454[npm:@tanstack/solid-start] + N455[npm:@tanstack/solid-start/server] + N456[npm:~/infrastructure/api/app-router] + N457[apps/server/src/infrastructure/api-clients/characters-api.ts] + N458[apps/server/src/infrastructure/api-clients/downloads-api.ts] + N459[apps/server/src/infrastructure/api-clients/fetch-url-api.ts] + N460[apps/server/src/infrastructure/api-clients/ips-api.ts] + N461[apps/server/src/infrastructure/api-clients/media-api.ts] + N462[apps/server/src/infrastructure/api-clients/search-api.ts] + N463[apps/server/src/infrastructure/api-clients/projects-api.ts] + N464[apps/server/src/infrastructure/api-clients/queries/index.ts] + N465[apps/server/node_modules/@orpc/solid-query/dist/index.d.mts] + N466[apps/server/src/infrastructure/api-clients/sources-api.ts] + N467[apps/server/src/infrastructure/api-clients/thumbnails.ts] + N468[apps/server/src/infrastructure/api/clients/preset-client.ts] + N469[apps/server/src/infrastructure/api/clients/search-history-client.ts] + N470[apps/server/src/infrastructure/api/routers/ai-router.ts] + N471[apps/server/src/infrastructure/api/routers/authors-router.ts] + N472[npm:@solid-imager/core/domain/contract/authors.contract] + N473[npm:~/infrastructure/repositories/authors-repository] + N474[apps/server/src/infrastructure/api/routers/categories-router.ts] + N475[npm:@solid-imager/core/domain/contract/categories.contract] + N476[npm:~/infrastructure/services/category-service] + N477[apps/server/src/infrastructure/api/routers/characters-router.ts] + N478[npm:@solid-imager/core/domain/contract/characters.contract] + N479[npm:~/infrastructure/services/character-service] + N480[apps/server/src/infrastructure/api/routers/entity-media-counts.ts] + N481[apps/server/src/infrastructure/api/routers/config-router.ts] + N482[npm:@solid-imager/core/domain/contract/config.contract] + N483[apps/server/src/infrastructure/api/routers/directories-router.ts] + N484[npm:@solid-imager/core/domain/contract/directories.contract] + N485[npm:~/infrastructure/services/directory-service] + N486[apps/server/src/infrastructure/api/routers/downloads-router.ts] + N487[npm:@solid-imager/core/domain/contract/downloads.contract] + N488[apps/server/src/infrastructure/api/routers/imports-router.ts] + N489[npm:@solid-imager/core/domain/contract/imports.contract] + N490[apps/server/src/infrastructure/api/routers/ips-router.ts] + N491[npm:@solid-imager/core/domain/contract/ips.contract] + N492[npm:~/infrastructure/services/ip-service] + N493[apps/server/src/infrastructure/api/routers/media-router.ts] + N494[npm:@solid-imager/core/domain/contract/media.contract] + N495[npm:@solid-imager/core/utils/async-pool] + N496[npm:~/infrastructure/logger] + N497[npm:~/infrastructure/services/bulk-operation-service] + N498[npm:~/infrastructure/services/ccip-vector-service] + N499[apps/server/src/infrastructure/api/routers/presets-router.ts] + N500[npm:@solid-imager/core/domain/contract/presets.contract] + N501[npm:~/infrastructure/services/preset-service] + N502[apps/server/src/infrastructure/api/routers/projects-router.ts] + N503[npm:@solid-imager/core/domain/contract/projects.contract] + N504[npm:~/infrastructure/services/project-service] + N505[apps/server/src/infrastructure/api/routers/sources-router.ts] + N506[apps/server/src/infrastructure/api/routers/tags-router.ts] + N507[npm:@solid-imager/core/domain/contract/tags.contract] + N508[npm:~/infrastructure/services/tag-service] + N509[apps/server/src/infrastructure/api/routers/thumbnails-router.ts] + N510[npm:@solid-imager/core/domain/contract/thumbnails.contract] + N511[npm:~/infrastructure/services/thumbnail-service] + N512[apps/server/src/infrastructure/api/routers/utils-router.ts] + N513[npm:@solid-imager/core/domain/contract/utils.contract] + N514[apps/server/src/infrastructure/api/routers/jobs-router.ts] + N515[npm:@solid-imager/core/domain/contract/jobs.contract] + N516[apps/server/src/infrastructure/api/routers/search-snapshots-router.ts] + N517[npm:@solid-imager/core/domain/contract/search-snapshots.contract] + N518[npm:~/infrastructure/services/search-snapshot-service] + N519[apps/server/src/infrastructure/api/job-artifact.ts] + N520[npm:~/infrastructure/repositories/job-repository] + N521[npm:~/infrastructure/services/job-transfer-storage] + N522[npm:~/infrastructure/utils/stream-utils] + N523[apps/server/src/infrastructure/api/app-router.ts] + N524[npm:~/infrastructure/api/routers/ai-router] + N525[npm:~/infrastructure/api/routers/authors-router] + N526[npm:~/infrastructure/api/routers/categories-router] + N527[npm:~/infrastructure/api/routers/characters-router] + N528[npm:~/infrastructure/api/routers/config-router] + N529[npm:~/infrastructure/api/routers/directories-router] + N530[npm:~/infrastructure/api/routers/downloads-router] + N531[npm:~/infrastructure/api/routers/imports-router] + N532[npm:~/infrastructure/api/routers/ips-router] + N533[npm:~/infrastructure/api/routers/jobs-router] + N534[npm:~/infrastructure/api/routers/media-router] + N535[npm:~/infrastructure/api/routers/presets-router] + N536[npm:~/infrastructure/api/routers/projects-router] + N537[npm:~/infrastructure/api/routers/search-snapshots-router] + N538[npm:~/infrastructure/api/routers/sources-router] + N539[npm:~/infrastructure/api/routers/tags-router] + N540[npm:~/infrastructure/api/routers/thumbnails-router] + N541[npm:~/infrastructure/api/routers/utils-router] + N542[apps/server/src/infrastructure/bootstrap.ts] + N543[npm:~/infrastructure/jobs/download-rate-limiter] + N544[apps/server/src/infrastructure/db/__mocks__/index.ts] + N545[npm:uuid] + N546[apps/server/src/infrastructure/db/connection.ts] + N547[apps/server/node_modules/@electric-sql/pglite/dist/index.cjs] + N548[apps/server/node_modules/pg/esm/index.mjs] + N549[apps/server/src/infrastructure/db/pglite.ts] + N550[apps/server/src/infrastructure/db/data-migration.ts] + N551[apps/server/src/infrastructure/db/executor.ts] + N552[npm:@solid-imager/db/types] + N553[apps/server/src/infrastructure/db/index.ts] + N554[apps/server/node_modules/drizzle-orm/node-postgres/index.d.ts] + N555[apps/server/node_modules/drizzle-orm/pglite/index.d.ts] + N556[apps/server/src/infrastructure/db/schema.ts] + N557[apps/server/node_modules/@electric-sql/pglite-pgvector/dist/index.cjs] + N558[apps/server/src/infrastructure/file-system/node-file-system.ts] + N559[apps/server/src/infrastructure/jobs/download-jobs.ts] + N560[apps/server/src/infrastructure/jobs/download-rate-limiter.ts] + N561[apps/server/src/infrastructure/jobs/file-watcher-service.ts] + N562[npm:~/infrastructure/jobs/file-watcher-manager] + N563[npm:~/infrastructure/services/directory-sync-service] + N564[apps/server/src/infrastructure/jobs/ccip-jobs.ts] + N565[apps/server/src/infrastructure/jobs/job-worker.ts] + N566[apps/server/src/infrastructure/jobs/tagging-jobs.ts] + N567[apps/server/src/infrastructure/jobs/tag-extraction.ts] + N568[apps/server/src/infrastructure/jobs/thumbnails.ts] + N569[apps/server/src/infrastructure/jobs/file-watcher-manager.ts] + N570[apps/server/node_modules/chokidar/index.js] + N571[apps/server/src/infrastructure/logger.ts] + N572[apps/server/node_modules/pino/pino.js] + N573[apps/server/src/infrastructure/processing/image-processor.ts] + N574[apps/server/src/infrastructure/repositories/author-repository.ts] + N575[npm:@solid-imager/db/repositories/author-repository] + N576[npm:~/infrastructure/db/executor] + N577[apps/server/src/infrastructure/repositories/authors-repository.ts] + N578[npm:@solid-imager/core/domain/repositories/authors-repository] + N579[npm:@solid-imager/db/repositories/authors-repository] + N580[apps/server/src/infrastructure/repositories/category-repository.ts] + N581[npm:@solid-imager/core/domain/repositories/category-repository] + N582[npm:@solid-imager/db/repositories/category-repository] + N583[apps/server/src/infrastructure/repositories/character-repository.ts] + N584[npm:@solid-imager/db/repositories/character-repository] + N585[apps/server/src/infrastructure/repositories/collection-repository.ts] + N586[npm:@solid-imager/core/domain/repositories/collection-repository] + N587[npm:@solid-imager/db/repositories/collection-repository] + N588[apps/server/src/infrastructure/repositories/ip-repository.ts] + N589[npm:@solid-imager/db/repositories/ip-repository] + N590[apps/server/src/infrastructure/repositories/job-repository.ts] + N591[npm:@solid-imager/db/repositories/job-repository] + N592[apps/server/src/infrastructure/repositories/media-repository-utils.ts] + N593[npm:@solid-imager/db/repositories/media-repository-utils] + N594[apps/server/src/infrastructure/repositories/media-repository.ts] + N595[npm:@solid-imager/db/repositories/media-repository] + N596[apps/server/src/infrastructure/repositories/preset-repository.ts] + N597[npm:@solid-imager/core/domain/repositories/preset-repository] + N598[npm:@solid-imager/db/repositories/preset-repository] + N599[apps/server/src/infrastructure/repositories/project-repository.ts] + N600[npm:@solid-imager/db/repositories/project-repository] + N601[apps/server/src/infrastructure/repositories/source-repository.ts] + N602[npm:@solid-imager/db/repositories/source-repository] + N603[apps/server/src/infrastructure/repositories/tag-repository.ts] + N604[npm:@solid-imager/db/repositories/tag-repository] + N605[apps/server/src/infrastructure/repositories/user-repository.ts] + N606[npm:@solid-imager/db/repositories/user-repository] + N607[apps/server/src/infrastructure/repositories/search-snapshot-repository.ts] + N608[npm:@solid-imager/db/repositories/search-snapshot-repository] + N609[apps/server/src/infrastructure/storage/factory.ts] + N610[apps/server/src/infrastructure/storage/local.ts] + N611[apps/server/src/infrastructure/storage/schema.ts] + N612[apps/server/src/infrastructure/storage/server-media-storage.ts] + N613[apps/server/src/infrastructure/utils/ffmpeg.ts] + N614[apps/server/src/infrastructure/utils/stream-utils.ts] + N615[apps/server/src/infrastructure/events/realtime-event-bus.ts] + N616[npm:node:events] + N617[apps/server/src/infrastructure/router/route-types.ts] + N618[apps/server/node_modules/@tanstack/solid-query/build/index.cjs] + N619[apps/server/src/infrastructure/server-route-bootstrap.ts] + N620[apps/server/src/infrastructure/services/author-service.ts] + N621[npm:@solid-imager/application/services/author-service] + N622[apps/server/src/infrastructure/services/backup-service.ts] + N623[apps/server/src/infrastructure/services/bulk-operation-service.ts] + N624[apps/server/src/infrastructure/services/category-service.ts] + N625[npm:@solid-imager/application/services/category-service] + N626[npm:~/infrastructure/repositories/category-repository] + N627[apps/server/src/infrastructure/services/ccip-vector-service.ts] + N628[npm:@solid-imager/application/ports/media-service] + N629[npm:~/infrastructure/ai/postgres-ccip-vector-store] + N630[npm:~/infrastructure/services/tagging-service] + N631[apps/server/src/infrastructure/services/collection-service.ts] + N632[npm:@solid-imager/application/services/collection-service] + N633[npm:~/infrastructure/repositories/collection-repository] + N634[apps/server/src/infrastructure/services/directory-service.ts] + N635[npm:~/infrastructure/services/media-source-service] + N636[npm:~/infrastructure/storage/factory] + N637[apps/server/src/infrastructure/services/directory-sync-service.ts] + N638[apps/server/src/infrastructure/services/ip-service.ts] + N639[npm:@solid-imager/application/services/ip-service] + N640[apps/server/src/infrastructure/services/job-dispatch-service.ts] + N641[apps/server/src/infrastructure/services/job-transfer-storage.ts] + N642[apps/server/src/infrastructure/services/maintenance-service.ts] + N643[apps/server/src/infrastructure/services/media-processing-service.ts] + N644[apps/server/src/infrastructure/services/preset-service.ts] + N645[npm:@solid-imager/application/services/preset-service] + N646[npm:~/infrastructure/repositories/preset-repository] + N647[apps/server/src/infrastructure/services/project-service.ts] + N648[npm:@solid-imager/application/services/project-service] + N649[apps/server/src/infrastructure/services/search-service.ts] + N650[npm:@solid-imager/application/services/search-service] + N651[apps/server/src/infrastructure/services/server-config-service.ts] + N652[npm:node:util] + N653[apps/server/src/infrastructure/services/source-transfer-job-service.ts] + N654[apps/server/src/infrastructure/services/tag-service.ts] + N655[npm:@solid-imager/application/services/tag-service] + N656[apps/server/src/infrastructure/services/tagging-service.ts] + N657[apps/server/src/infrastructure/services/thumbnail-service.ts] + N658[npm:@solid-imager/core/domain/thumbnails/schemas] + N659[apps/server/src/infrastructure/services/user-service.ts] + N660[npm:@solid-imager/application/services/user-service] + N661[npm:~/infrastructure/repositories/user-repository] + N662[apps/server/src/infrastructure/services/search-snapshot-service.ts] + N663[npm:@solid-imager/application/services/search-snapshot-service] + N664[npm:~/infrastructure/repositories/search-snapshot-repository] + N665[apps/server/src/routes/$.tsx] + N666[apps/server/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] + N667[apps/server/src/routes/__root.tsx] + N668[apps/server/src/routes/about.tsx] + N669[npm:@solid-imager/ui/counter] + N670[apps/server/src/routes/api/rpc.$.ts] + N671[npm:~/infrastructure/router/route-types] + N672[npm:~/infrastructure/server-route-bootstrap] + N673[apps/server/src/routes/api/sources.$mediaSourceId.$mediaId.ts] + N674[npm:@solid-imager/core/domain/media/utils/media-type-utils] + N675[apps/server/src/routes/api/jobs.$jobId.artifact.ts] + N676[apps/server/src/routes/api/sources.$mediaSourceId.thumbnail.$mediaId.ts] + N677[apps/server/src/routes/config.tsx] + N678[npm:~/infrastructure/api-clients/queries] + N679[apps/server/src/routes/v2/$.tsx] + N680[npm:@solid-imager/ui/v2/icons] + N681[apps/server/src/routes/v2/about.tsx] + N682[apps/server/src/routes/v2/config.tsx] + N683[npm:@solid-imager/ui/screens/v2-config-state-screen] + N684[apps/server/src/routes/v2/index.tsx] + N685[apps/server/src/routes/v2/jobs.tsx] + N686[npm:@solid-imager/ui/hooks/use-job-events] + N687[npm:@solid-imager/ui/screens/v2-jobs-screen] + N688[apps/server/src/routes/v2/manager.tsx] + N689[npm:@solid-imager/ui/hooks/use-manager-page] + N690[npm:@solid-imager/ui/screens/v2-manager/types] + N691[npm:@solid-imager/ui/screens/v2-manager-screen] + N692[apps/server/src/routes/v2/media-context.ts] + N693[apps/server/src/routes/v2/route.tsx] + N694[apps/server/node_modules/solid-js/types/index.d.ts] + N695[npm:~/components/api-activity-indicator] + N696[npm:~/components/v2/v2-app-shell] + N697[apps/server/src/routes/v2/search.tsx] + N698[apps/server/src/routes/v2/sources/$mediaSourceId/$mediaId/index.tsx] + N699[npm:@solid-imager/ui/screens/v2-media-detail-screen] + N700[apps/server/src/routes/v2/sources/$mediaSourceId/index.tsx] + N701[npm:~/routes/sources/$mediaSourceId/components/v2-source-media-page] + N702[apps/server/src/routes/v2/components/v2-search-content.tsx] + N703[npm:@solid-imager/ui/screens/v2-search-screen] + N704[npm:~/components/media/thumbnail-image] + N705[npm:~/components/media/v2-media-grid-item] + N706[apps/server/src/routes/docs/swagger/index.tsx] + N707[apps/server/src/routes/index.tsx] + N708[apps/server/src/routes/manager.tsx] + N709[npm:@solid-imager/ui/screens/manager-screen] + N710[apps/server/src/routes/search.tsx] + N711[npm:~/components/media/legacy-media-grid-item] + N712[apps/server/src/routes/sources/$mediaSourceId/$mediaId/index.tsx] + N713[apps/server/src/routes/sources/$mediaSourceId/components/source-media-page.tsx] + N714[npm:@solid-imager/ui/screens/source-media-screen.types] + N715[apps/server/src/routes/sources/$mediaSourceId/components/legacy-source-media-page.tsx] + N716[apps/server/src/routes/sources/$mediaSourceId/components/v2-source-media-page.tsx] + N717[npm:@solid-imager/ui/screens/v2-source-media-screen] + N718[npm:~/components/v2-upload-media-modal] + N719[npm:~/routes/v2/media-context] + N720[apps/server/src/routes/sources/$mediaSourceId/index.tsx] + N721[apps/server/src/routes/sources/index.tsx] + N722[apps/server/src/routes/design-lab.tsx] + N723[npm:@solid-imager/ui/screens/design-concept-screen] + N724[apps/server/src/components/imports/pending-downloads-indicator.tsx] + N725[apps/server/src/components/imports/pending-downloads-indicator-data.ts] + N726[apps/server/src/components/imports/v2-pending-downloads-indicator.tsx] + N727[npm:@solid-imager/ui/v2-pending-downloads-indicator] + N728[apps/server/src/components/media/ai-tagging-modal.tsx] + N729[apps/server/src/components/media/association-manager.tsx] + N730[apps/server/src/components/media/bulk-action-dialog.tsx] + N731[apps/server/src/components/media/character-crop-modal.tsx] + N732[apps/server/src/components/media/search-filters.tsx] + N733[npm:@solid-imager/core/domain/projects/schemas] + N734[npm:@solid-imager/core/domain/tags/schemas] + N735[apps/server/src/components/media/media-viewer.tsx] + N736[apps/server/src/components/media/move-copy-media-dialog.tsx] + N737[apps/server/src/components/media/preset-manager.tsx] + N738[npm:@solid-imager/ui/preset-manager] + N739[apps/server/src/components/media/pro-search-builder.tsx] + N740[apps/server/src/components/media/pro-search-dialog.tsx] + N741[apps/server/src/components/media/search-control-panel.tsx] + N742[npm:@solid-imager/ui/label] + N743[apps/server/src/components/media/sort-controls.tsx] + N744[apps/server/src/components/media/thumbnail-image.tsx] + N745[npm:@solid-imager/ui/thumbnail-image] + N746[apps/server/src/components/media/oppai-oracle-modal.tsx] + N747[npm:@solid-imager/ui/oppai-oracle-modal] + N748[apps/server/src/components/media/legacy-media-grid-item.tsx] + N749[apps/server/src/components/media/legacy-media-sidebar.tsx] + N750[npm:@solid-imager/ui/clipboard-copy] + N751[npm:@solid-imager/ui/collapsible] + N752[apps/server/src/components/media/v2-media-actions.tsx] + N753[apps/server/src/components/media/v2-media-sidebar.tsx] + N754[apps/server/src/components/media/v2-media-viewer.tsx] + N755[apps/server/src/components/nav.tsx] + N756[apps/server/src/components/simple-modal.tsx] + N757[apps/server/src/components/swagger-ui.tsx] + N758[apps/server/node_modules/swagger-ui-dist/swagger-ui-bundle.js] + N759[apps/server/node_modules/swagger-ui-dist/swagger-ui.css] + N760[apps/server/src/components/upload-media-modal.tsx] + N761[npm:@solid-imager/ui/legacy-upload-media-modal-content] + N762[npm:~/infrastructure/api-clients/fetch-url-api] + N763[apps/server/src/components/api-activity-indicator.tsx] + N764[apps/server/src/components/v2-upload-media-modal.tsx] + N765[npm:@solid-imager/ui/v2-upload-media-modal-content] + N766[apps/server/src/components/v2/v2-mobile-header.tsx] + N767[npm:~/components/imports/v2-pending-downloads-indicator] + N768[apps/server/src/components/v2/v2-sidebar.tsx] + N769[apps/server/src/components/v2/v2-source-list.tsx] + N770[apps/server/src/config/database.ts] + N771[apps/server/src/routeTree.gen.ts] + N772[apps/server/src/app.css] + N773[packages/ui/src/ai-tagging-modal.tsx] + N774[packages/ui/node_modules/solid-js/types/index.d.ts] + N775[packages/ui/src/badge.tsx] + N776[packages/ui/src/association-manager.tsx] + N777[packages/ui/src/button.tsx] + N778[packages/ui/node_modules/class-variance-authority/dist/index.d.ts] + N779[packages/ui/src/utils/cn.ts] + N780[packages/ui/src/card.tsx] + N781[packages/ui/src/character-crop-modal.tsx] + N782[packages/ui/src/checkbox.tsx] + N783[packages/ui/src/clipboard-copy.tsx] + N784[packages/ui/src/toast.tsx] + N785[packages/ui/src/collapsible.tsx] + N786[packages/ui/node_modules/@kobalte/core/dist/index.d.ts] + N787[packages/ui/src/combobox.tsx] + N788[npm:@kobalte/core/combobox] + N789[npm:@kobalte/core/polymorphic] + N790[packages/ui/node_modules/@tanstack/solid-virtual/dist/cjs/index.cjs] + N791[packages/ui/src/command.tsx] + N792[npm:@kobalte/core/dialog] + N793[packages/ui/node_modules/cmdk-solid/dist/index.cjs] + N794[packages/ui/src/counter.tsx] + N795[packages/ui/src/dummy.test.ts] + N796[packages/ui/node_modules/vitest/dist/index.js] + N797[packages/ui/src/hooks/use-manager-page.ts] + N798[packages/ui/src/hooks/use-search-page.ts] + N799[packages/ui/src/hooks/use-source-media-page.test.ts] + N800[packages/ui/src/hooks/restore-import.ts] + N801[packages/ui/src/hooks/use-source-media-page.ts] + N802[packages/ui/src/hooks/use-source-root-path.test.ts] + N803[packages/ui/src/hooks/use-source-root-path.ts] + N804[packages/ui/node_modules/@tanstack/solid-query/build/index.cjs] + N805[packages/ui/src/hooks/use-batch-job-events.test.ts] + N806[packages/ui/src/hooks/use-current-search-persistence.test.ts] + N807[packages/ui/src/hooks/scroll-container.ts] + N808[packages/ui/node_modules/solid-js/web/types/index.d.ts] + N809[packages/ui/src/hooks/use-job-events.ts] + N810[packages/ui/src/event-stream.ts] + N811[packages/ui/src/import-inbox-helpers.ts] + N812[packages/ui/src/input.tsx] + N813[packages/ui/src/label.tsx] + N814[packages/ui/src/layouts/app-shell.tsx] + N815[packages/ui/src/media-card-item.tsx] + N816[packages/ui/src/media-grid-item.tsx] + N817[packages/ui/src/media-list-actions.tsx] + N818[packages/ui/node_modules/@tanstack/solid-router/dist/cjs/index.cjs] + N819[packages/ui/src/media-sidebar-content.tsx] + N820[packages/ui/src/media-sidebar.tsx] + N821[packages/ui/src/move-copy-media-dialog.tsx] + N822[packages/ui/src/pagination-controls.tsx] + N823[packages/ui/src/pending-downloads-indicator.tsx] + N824[packages/ui/src/import-review-modal.tsx] + N825[packages/ui/src/pending-downloads-indicator.types.ts] + N826[packages/ui/src/pending-downloads-indicator-core.tsx] + N827[packages/ui/src/popover.tsx] + N828[npm:@kobalte/core/popover] + N829[packages/ui/src/preset-client.ts] + N830[packages/ui/src/pro-search-builder.tsx] + N831[packages/ui/src/pro-search-dialog.tsx] + N832[packages/ui/src/query-options/authors-query.ts] + N833[packages/ui/src/query-options/characters-query.ts] + N834[packages/ui/src/query-options/config-query.ts] + N835[packages/ui/src/query-options/ips-query.ts] + N836[packages/ui/src/query-options/media-query.ts] + N837[npm:@solid-imager/core/domain/shared/schemas] + N838[packages/ui/src/query-options/projects-query.ts] + N839[packages/ui/src/query-options/sources-query.ts] + N840[packages/ui/src/query-options/tags-query.ts] + N841[packages/ui/src/query-options/prefetch.ts] + N842[packages/ui/src/query-options/query-client.test.ts] + N843[packages/ui/src/query-options/query-client.ts] + N844[packages/ui/src/screens/config-screen.tsx] + N845[packages/ui/node_modules/@tanstack/solid-form/dist/cjs/index.cjs] + N846[packages/ui/node_modules/zod/index.d.cts] + N847[packages/ui/src/screens/manager-screen.tsx] + N848[packages/ui/src/screens/not-found-screen.tsx] + N849[packages/ui/src/screens/search-screen.tsx] + N850[packages/ui/src/async-state.tsx] + N851[packages/ui/src/mobile-search-filter-dialog.tsx] + N852[packages/ui/src/search-control-panel.tsx] + N853[packages/ui/src/skeleton.tsx] + N854[packages/ui/src/source-media-grid.tsx] + N855[packages/ui/src/screens/search-screen.types.ts] + N856[packages/ui/src/screens/source-media-screen.tsx] + N857[packages/ui/src/screens/config-state-screen.types.ts] + N858[packages/ui/src/query-state.ts] + N859[packages/ui/src/screens/design-concept-screen.tsx] + N860[npm:lucide-solid/icons/arrow-down-up] + N861[npm:lucide-solid/icons/arrow-left] + N862[npm:lucide-solid/icons/ban] + N863[npm:lucide-solid/icons/bot] + N864[npm:lucide-solid/icons/briefcase-business] + N865[npm:lucide-solid/icons/chevron-down] + N866[npm:lucide-solid/icons/chevron-left] + N867[npm:lucide-solid/icons/chevron-right] + N868[npm:lucide-solid/icons/circle-alert] + N869[npm:lucide-solid/icons/circle-check] + N870[npm:lucide-solid/icons/clock-3] + N871[npm:lucide-solid/icons/cloud-download] + N872[npm:lucide-solid/icons/database] + N873[npm:lucide-solid/icons/download] + N874[npm:lucide-solid/icons/external-link] + N875[npm:lucide-solid/icons/filter] + N876[npm:lucide-solid/icons/folder] + N877[npm:lucide-solid/icons/grid-3-x-3] + N878[npm:lucide-solid/icons/hard-drive] + N879[npm:lucide-solid/icons/image] + N880[npm:lucide-solid/icons/inbox] + N881[npm:lucide-solid/icons/library] + N882[npm:lucide-solid/icons/list] + N883[npm:lucide-solid/icons/logs] + N884[npm:lucide-solid/icons/panel-left-close] + N885[npm:lucide-solid/icons/panel-left-open] + N886[npm:lucide-solid/icons/panels-top-left] + N887[npm:lucide-solid/icons/plus] + N888[npm:lucide-solid/icons/refresh-cw] + N889[npm:lucide-solid/icons/rotate-ccw] + N890[npm:lucide-solid/icons/search] + N891[npm:lucide-solid/icons/settings] + N892[npm:lucide-solid/icons/share-2] + N893[npm:lucide-solid/icons/trash-2] + N894[npm:lucide-solid/icons/x] + N895[packages/ui/src/screens/legacy-config-state-screen.tsx] + N896[packages/ui/src/screens/legacy-media-detail-screen.tsx] + N897[packages/ui/src/legacy-media-detail-skeleton.tsx] + N898[packages/ui/src/screens/media-detail-screen.types.ts] + N899[packages/ui/src/screens/media-detail-screen-core.tsx] + N900[packages/ui/src/screens/source-media-screen.types.ts] + N901[packages/ui/src/screens/v2-config-screen.tsx] + N902[packages/ui/src/screens/v2-config-state-screen.tsx] + N903[packages/ui/src/v2/management-layout.tsx] + N904[packages/ui/src/screens/v2-manager-screen.tsx] + N905[packages/ui/src/screens/v2-manager/batch-tools.tsx] + N906[packages/ui/src/screens/v2-manager/job-status.tsx] + N907[packages/ui/src/screens/v2-manager/source-select.tsx] + N908[packages/ui/src/screens/v2-manager/data-transfer.tsx] + N909[npm:lucide-solid/icons/upload] + N910[packages/ui/src/screens/v2-manager/dialogs.tsx] + N911[packages/ui/src/screens/v2-manager/duplicates.tsx] + N912[packages/ui/src/screens/v2-manager/entity-panel.tsx] + N913[npm:lucide-solid/icons/pencil] + N914[packages/ui/src/progress.tsx] + N915[packages/ui/src/screens/v2-manager/navigation.tsx] + N916[npm:lucide-solid/icons/copy-check] + N917[packages/ui/src/screens/v2-manager/thumbnail.tsx] + N918[packages/ui/src/screens/v2-manager/types.ts] + N919[packages/ui/src/screens/v2-manager/utils.test.ts] + N920[packages/ui/src/screens/v2-manager/utils.ts] + N921[packages/ui/src/screens/v2-media-detail-screen.tsx] + N922[packages/ui/src/v2-media-detail-skeleton.tsx] + N923[packages/ui/src/screens/v2-search-screen.tsx] + N924[packages/ui/src/screens/v2-source-media-screen.tsx] + N925[npm:@solid-imager/core/domain/search/schema] + N926[packages/ui/node_modules/solid-js/store/types/index.d.ts] + N927[packages/ui/src/preset-manager.tsx] + N928[packages/ui/src/search-filters.tsx] + N929[packages/ui/src/select.tsx] + N930[packages/ui/src/sort-controls.tsx] + N931[packages/ui/src/source-delete-modal.tsx] + N932[packages/ui/src/source-media-page.tsx] + N933[packages/ui/src/stores/search-store.ts] + N934[packages/ui/src/stores/search-store.test.ts] + N935[packages/ui/src/switch.tsx] + N936[packages/ui/src/tabs.tsx] + N937[packages/ui/src/textarea.tsx] + N938[packages/ui/src/thumbnail-image.tsx] + N939[packages/ui/src/thumbnail-source.ts] + N940[packages/ui/node_modules/clsx/dist/clsx.js] + N941[packages/ui/node_modules/tailwind-merge/dist/types.d.ts] + N942[packages/ui/src/utils/debounce.ts] + N943[packages/ui/src/event-stream.test.ts] + N944[packages/ui/src/form-message.tsx] + N945[packages/ui/src/form-schemas.test.ts] + N946[packages/ui/src/form-schemas.ts] + N947[packages/ui/src/oppai-oracle-modal.tsx] + N948[packages/ui/src/query-state.test.ts] + N949[packages/ui/src/router-status.tsx] + N950[packages/ui/src/screen-skeleton.tsx] + N951[packages/ui/src/text-field.tsx] + N952[npm:@kobalte/core/text-field] + N953[packages/ui/src/import-review-modal.types.ts] + N954[packages/ui/src/legacy-import-review-modal.tsx] + N955[packages/ui/src/legacy-upload-media-modal.tsx] + N956[packages/ui/src/media-preview-selection.test.ts] + N957[packages/ui/src/media-preview-selection.ts] + N958[packages/ui/src/v2/collection-inspector.tsx] + N959[packages/ui/src/v2/icons.tsx] + N960[packages/ui/src/v2/search-composer-utils.ts] + N961[packages/ui/src/v2/search-composer.test.ts] + N962[packages/ui/src/v2/search-composer.tsx] + N963[packages/ui/src/v2/search-toolbar.tsx] + N964[packages/ui/src/thumbnail-source.test.ts] + N965[packages/ui/src/v2-import-review-modal.tsx] + N966[packages/ui/src/v2-media-grid-item.tsx] + N967[packages/ui/src/v2-pending-downloads-indicator.tsx] + N968[packages/ui/src/v2-upload-media-modal-content.tsx] + N969[packages/ui/src/upload-media-modal-content.types.ts] + N970[packages/ui/src/v2-upload-media-modal.tsx] + N971[packages/ui/src/search-history-client.ts] + N972[packages/ui/src/search-history-route.ts] N0 --> N1 N0 --> N2 N0 --> N3 @@ -980,1705 +1010,1779 @@ graph LR N5 --> N17 N5 --> N4 N18 --> N19 - N20 --> N21 - N20 --> N22 - N22 --> N23 + N20 --> N19 + N21 --> N22 + N21 --> N23 + N21 --> N19 N24 --> N25 - N24 --> N22 - N26 --> N27 - N26 --> N28 - N29 --> N30 - N29 --> N31 - N32 --> N33 - N32 --> N31 - N34 --> N35 - N34 --> N36 - N34 --> N28 - N37 --> N38 - N37 --> N39 - N37 --> N40 - N37 --> N41 - N37 --> N42 - N37 --> N30 - N37 --> N31 + N24 --> N26 + N24 --> N27 + N24 --> N28 + N24 --> N29 + N24 --> N30 + N24 --> N31 + N24 --> N32 + N33 --> N34 + N33 --> N35 + N33 --> N36 + N33 --> N37 + N33 --> N19 + N33 --> N38 + N33 --> N39 + N40 --> N34 + N40 --> N35 + N40 --> N36 + N40 --> N37 + N40 --> N19 + N40 --> N38 + N40 --> N39 + N31 --> N37 + N31 --> N33 + N31 --> N40 + N31 --> N41 + N31 --> N42 + N31 --> N43 + N31 --> N44 + N41 --> N34 + N41 --> N35 + N41 --> N36 + N41 --> N37 + N41 --> N19 + N41 --> N38 + N41 --> N39 + N42 --> N34 + N42 --> N35 + N42 --> N36 + N42 --> N37 + N42 --> N19 + N42 --> N38 + N42 --> N39 + N43 --> N34 N43 --> N35 - N44 --> N33 - N44 --> N45 - N44 --> N46 - N44 --> N47 - N48 --> N49 - N48 --> N50 - N48 --> N51 - N52 --> N38 - N52 --> N39 - N52 --> N40 - N53 --> N38 - N53 --> N39 - N53 --> N40 - N53 --> N35 - N53 --> N41 - N53 --> N42 - N53 --> N31 - N54 --> N38 - N54 --> N39 - N54 --> N40 - N54 --> N41 - N54 --> N33 - N54 --> N42 - N54 --> N31 - N54 --> N55 - N56 --> N55 - N57 --> N35 + N43 --> N36 + N43 --> N37 + N43 --> N19 + N43 --> N38 + N43 --> N39 + N44 --> N34 + N44 --> N35 + N44 --> N36 + N44 --> N37 + N44 --> N19 + N44 --> N38 + N44 --> N39 + N45 --> N46 + N47 --> N48 + N47 --> N49 + N50 --> N51 + N50 --> N52 + N53 --> N54 + N53 --> N55 + N53 --> N52 + N56 --> N54 + N57 --> N54 N57 --> N58 - N59 --> N60 - N59 --> N28 - N61 --> N35 - N62 --> N35 - N62 --> N17 - N62 --> N30 + N57 --> N59 + N57 --> N27 + N57 --> N60 + N61 --> N54 + N62 --> N22 N62 --> N63 + N62 --> N28 N62 --> N64 - N62 --> N65 - N62 --> N66 - N62 --> N67 - N62 --> N68 - N69 --> N35 - N69 --> N17 - N69 --> N31 - N70 --> N35 - N70 --> N17 - N71 --> N35 - N72 --> N73 - N72 --> N20 - N74 --> N31 - N74 --> N46 - N75 --> N46 - N75 --> N76 - N75 --> N77 + N65 --> N54 + N66 --> N67 + N66 --> N47 + N68 --> N69 + N30 --> N70 + N71 --> N19 + N72 --> N19 + N73 --> N19 + N74 --> N19 + N75 --> N19 + N76 --> N54 + N76 --> N19 + N77 --> N52 N78 --> N79 - N78 --> N80 - N81 --> N67 + N78 --> N19 + N80 --> N81 + N80 --> N19 + N82 --> N36 N82 --> N83 - N82 --> N80 - N84 --> N31 N84 --> N85 - N84 --> N86 - N87 --> N33 - N87 --> N31 - N88 --> N33 - N88 --> N31 - N89 --> N9 - N89 --> N10 + N86 --> N87 + N86 --> N88 N89 --> N90 - N91 --> N92 - N91 --> N10 - N91 --> N93 - N91 --> N94 - N91 --> N95 - N96 --> N97 - N96 --> N90 - N96 --> N98 - N99 --> N98 - N100 --> N35 - N100 --> N98 - N101 --> N98 - N102 --> N98 - N103 --> N98 - N103 --> N104 - N105 --> N106 - N105 --> N93 - N105 --> N94 - N105 --> N107 - N105 --> N108 - N105 --> N109 - N110 --> N98 - N111 --> N112 - N104 --> N35 - N104 --> N98 - N113 --> N93 - N113 --> N33 - N113 --> N90 - N113 --> N98 - N114 --> N98 - N115 --> N116 - N115 --> N98 - N117 --> N92 - N117 --> N10 - N117 --> N106 - N118 --> N106 - N118 --> N119 - N118 --> N120 - N121 --> N106 + N89 --> N91 + N89 --> N87 + N92 --> N93 + N32 --> N90 + N32 --> N94 + N95 --> N96 + N95 --> N27 + N97 --> N25 + N97 --> N26 + N97 --> N98 + N97 --> N27 + N97 --> N99 + N97 --> N38 + N100 --> N101 + N102 --> N94 + N102 --> N103 + N102 --> N104 + N102 --> N105 + N102 --> N27 + N102 --> N52 + N102 --> N106 + N107 --> N101 + N107 --> N108 + N109 --> N108 + N109 --> N110 + N109 --> N111 + N109 --> N112 + N109 --> N113 + N109 --> N114 + N109 --> N115 + N109 --> N116 + N109 --> N27 + N109 --> N117 + N109 --> N118 + N109 --> N119 + N109 --> N120 N121 --> N122 + N121 --> N94 + N121 --> N26 N121 --> N123 - N124 --> N106 - N124 --> N125 - N124 --> N126 - N124 --> N127 - N128 --> N106 - N128 --> N129 - N128 --> N130 - N131 --> N106 - N131 --> N132 - N131 --> N133 - N134 --> N106 - N134 --> N135 - N134 --> N136 - N137 --> N106 - N137 --> N138 - N137 --> N35 - N137 --> N139 - N137 --> N140 - N137 --> N90 - N137 --> N141 - N137 --> N142 - N137 --> N143 - N137 --> N136 - N137 --> N144 - N145 --> N106 - N145 --> N146 - N145 --> N147 - N145 --> N127 - N148 --> N106 - N148 --> N149 - N148 --> N150 - N148 --> N151 - N148 --> N152 - N148 --> N153 - N148 --> N154 - N148 --> N155 - N156 --> N106 + N121 --> N105 + N121 --> N27 + N121 --> N124 + N121 --> N125 + N121 --> N118 + N121 --> N106 + N126 --> N122 + N126 --> N113 + N126 --> N127 + N126 --> N115 + N126 --> N128 + N126 --> N27 + N126 --> N117 + N126 --> N129 + N126 --> N130 + N126 --> N118 + N126 --> N119 + N126 --> N120 + N131 --> N116 + N131 --> N27 + N132 --> N22 + N132 --> N48 + N132 --> N133 + N132 --> N134 + N132 --> N135 + N132 --> N103 + N132 --> N136 + N132 --> N137 + N132 --> N138 + N132 --> N139 + N132 --> N105 + N132 --> N27 + N132 --> N140 + N132 --> N141 + N132 --> N52 + N142 --> N96 + N142 --> N27 + N142 --> N28 + N143 --> N90 + N144 --> N97 + N144 --> N107 + N144 --> N95 + N144 --> N100 + N144 --> N102 + N144 --> N143 + N144 --> N145 + N144 --> N109 + N144 --> N146 + N144 --> N132 + N144 --> N142 + N144 --> N131 + N144 --> N121 + N147 --> N148 + N147 --> N91 + N149 --> N150 + N149 --> N151 + N149 --> N152 + N153 --> N154 + N153 --> N155 + N156 --> N154 + N156 --> N153 N156 --> N157 N156 --> N158 - N159 --> N106 + N158 --> N154 + N158 --> N155 + N157 --> N154 + N157 --> N155 N159 --> N160 - N159 --> N161 - N159 --> N127 - N162 --> N106 - N163 --> N106 - N163 --> N164 - N163 --> N165 - N166 --> N106 - N166 --> N167 - N166 --> N168 - N169 --> N106 + N159 --> N158 + N161 --> N162 + N161 --> N163 + N161 --> N164 + N161 --> N165 + N161 --> N166 + N161 --> N167 + N161 --> N168 + N169 --> N152 + N169 --> N154 N169 --> N170 - N171 --> N106 - N171 --> N172 - N127 --> N140 - N127 --> N141 - N173 --> N9 - N173 --> N174 - N173 --> N175 - N173 --> N176 - N173 --> N177 - N173 --> N178 - N179 --> N106 - N179 --> N94 - N179 --> N180 - N179 --> N181 - N179 --> N182 - N179 --> N183 - N179 --> N184 - N179 --> N185 - N179 --> N186 - N179 --> N187 - N179 --> N188 - N179 --> N189 - N179 --> N190 - N179 --> N191 - N179 --> N192 - N179 --> N193 - N179 --> N194 - N179 --> N195 - N179 --> N196 - N197 --> N198 - N197 --> N199 - N197 --> N143 - N197 --> N200 - N197 --> N201 - N197 --> N202 - N203 --> N204 - N203 --> N205 - N203 --> N142 - N206 --> N207 - N206 --> N208 - N206 --> N209 - N206 --> N210 - N211 --> N140 - N211 --> N212 - N213 --> N214 - N213 --> N212 - N215 --> N10 - N215 --> N207 - N215 --> N216 - N215 --> N217 - N215 --> N208 - N215 --> N152 - N215 --> N210 - N215 --> N218 - N210 --> N207 - N210 --> N219 - N220 --> N174 - N220 --> N221 - N222 --> N174 - N222 --> N92 - N222 --> N10 - N223 --> N224 - N225 --> N10 - N225 --> N143 - N225 --> N226 - N225 --> N227 - N225 --> N152 - N225 --> N228 - N225 --> N229 - N225 --> N130 - N225 --> N154 - N225 --> N230 - N225 --> N231 - N225 --> N232 - N233 --> N224 - N233 --> N234 - N233 --> N142 - N233 --> N143 - N233 --> N152 - N235 --> N97 - N235 --> N140 - N235 --> N90 - N235 --> N141 - N236 --> N237 - N236 --> N238 - N239 --> N240 - N241 --> N174 - N241 --> N10 - N241 --> N97 - N242 --> N10 - N242 --> N243 - N242 --> N143 - N242 --> N152 - N244 --> N245 - N246 --> N247 - N248 --> N249 - N248 --> N250 - N248 --> N251 + N169 --> N171 + N172 --> N173 + N174 --> N175 + N176 --> N175 + N177 --> N54 + N178 --> N179 + N178 --> N180 + N178 --> N181 + N182 --> N10 + N182 --> N183 + N182 --> N175 + N184 --> N10 + N184 --> N185 + N184 --> N186 + N187 --> N175 + N188 --> N10 + N188 --> N185 + N188 --> N186 + N189 --> N10 + N189 --> N185 + N189 --> N186 + N190 --> N10 + N190 --> N191 + N190 --> N192 + N190 --> N180 + N190 --> N193 + N190 --> N194 + N190 --> N195 + N190 --> N196 + N190 --> N22 + N190 --> N197 + N198 --> N199 + N200 --> N201 + N200 --> N186 + N200 --> N202 + N203 --> N201 + N203 --> N9 + N203 --> N14 + N204 --> N205 + N204 --> N206 + N204 --> N207 + N204 --> N208 + N209 --> N210 + N209 --> N208 + N211 --> N210 + N211 --> N208 + N211 --> N204 + N212 --> N213 + N212 --> N214 + N215 --> N213 + N216 --> N213 + N217 --> N213 + N218 --> N213 + N219 --> N220 + N219 --> N213 + N221 --> N220 + N221 --> N212 + N222 --> N220 + N222 --> N213 + N222 --> N215 + N223 --> N220 + N223 --> N213 + N224 --> N220 + N224 --> N218 + N225 --> N220 + N225 --> N213 + N226 --> N220 + N226 --> N213 + N226 --> N214 + N227 --> N220 + N227 --> N213 + N228 --> N219 + N228 --> N221 + N228 --> N222 + N228 --> N223 + N228 --> N224 + N228 --> N225 + N228 --> N226 + N228 --> N227 + N228 --> N229 + N228 --> N230 + N228 --> N231 + N228 --> N232 + N228 --> N233 + N228 --> N234 + N228 --> N235 + N228 --> N236 + N228 --> N237 + N228 --> N238 + N229 --> N220 + N229 --> N213 + N229 --> N239 + N231 --> N220 + N231 --> N213 + N232 --> N220 + N232 --> N213 + N233 --> N220 + N233 --> N213 + N235 --> N220 + N235 --> N213 + N235 --> N240 + N235 --> N241 + N236 --> N220 + N236 --> N213 + N237 --> N220 + N237 --> N213 + N238 --> N220 + N238 --> N213 + N230 --> N220 + N230 --> N213 + N242 --> N220 + N242 --> N232 + N234 --> N220 + N243 --> N220 + N243 --> N234 + N244 --> N213 + N239 --> N213 + N214 --> N213 + N245 --> N213 + N246 --> N214 + N247 --> N248 + N249 --> N213 + N250 --> N251 + N250 --> N248 N252 --> N253 - N252 --> N254 - N252 --> N251 - N255 --> N256 - N255 --> N257 - N255 --> N251 - N258 --> N259 - N258 --> N260 + N254 --> N255 + N254 --> N251 + N256 --> N251 + N256 --> N257 N258 --> N251 - N261 --> N262 - N261 --> N263 + N259 --> N251 + N260 --> N251 N261 --> N251 - N264 --> N265 - N264 --> N266 - N264 --> N251 - N267 --> N175 + N261 --> N248 + N261 --> N262 + N263 --> N264 + N265 --> N213 + N265 --> N248 + N266 --> N213 + N266 --> N265 + N267 --> N213 N267 --> N268 - N267 --> N251 - N269 --> N270 - N269 --> N251 - N271 --> N272 - N271 --> N273 - N271 --> N270 - N271 --> N251 - N271 --> N152 - N271 --> N274 - N271 --> N238 + N269 --> N213 + N241 --> N213 + N241 --> N270 + N241 --> N271 + N271 --> N213 + N272 --> N273 + N274 --> N213 N275 --> N276 N275 --> N277 - N275 --> N251 - N278 --> N279 - N278 --> N280 - N278 --> N251 - N281 --> N282 - N281 --> N283 - N281 --> N251 + N277 --> N213 + N278 --> N213 + N240 --> N213 + N270 --> N213 + N279 --> N280 + N281 --> N213 + N281 --> N268 + N282 --> N276 + N283 --> N201 + N283 --> N186 N284 --> N285 - N284 --> N286 - N284 --> N251 - N287 --> N288 - N287 --> N289 - N287 --> N251 - N290 --> N33 + N286 --> N287 + N288 --> N186 + N289 --> N186 N290 --> N291 N290 --> N292 - N291 --> N9 - N291 --> N174 - N291 --> N10 - N291 --> N33 - N291 --> N292 - N292 --> N9 - N292 --> N13 - N293 --> N174 - N293 --> N10 + N290 --> N286 + N293 --> N202 N294 --> N295 - N294 --> N152 - N296 --> N13 - N297 --> N298 - N299 --> N67 - N300 --> N197 - N301 --> N302 - N301 --> N274 - N303 --> N9 - N303 --> N174 - N303 --> N10 - N303 --> N13 - N303 --> N14 - N304 --> N35 - N304 --> N227 - N304 --> N152 - N304 --> N130 + N294 --> N296 + N292 --> N297 + N292 --> N298 + N292 --> N294 + N299 --> N300 + N299 --> N301 + N302 --> N300 + N302 --> N301 + N303 --> N300 + N303 --> N301 + N304 --> N300 + N304 --> N301 + N305 --> N54 + N305 --> N300 N305 --> N306 - N305 --> N307 - N308 --> N309 - N308 --> N310 - N308 --> N311 - N308 --> N141 - N308 --> N130 - N308 --> N312 - N313 --> N314 - N313 --> N315 - N316 --> N317 - N316 --> N318 - N319 --> N174 - N319 --> N10 - N319 --> N33 - N319 --> N143 - N319 --> N227 - N319 --> N152 - N319 --> N228 - N319 --> N229 - N319 --> N130 - N319 --> N154 - N319 --> N231 - N320 --> N321 - N320 --> N322 - N323 --> N309 - N323 --> N175 - N323 --> N143 - N324 --> N9 - N324 --> N174 - N324 --> N10 - N324 --> N14 - N324 --> N175 - N324 --> N178 - N325 --> N174 - N325 --> N10 - N325 --> N272 - N325 --> N282 - N325 --> N234 - N325 --> N227 - N325 --> N152 + N305 --> N301 + N307 --> N54 + N307 --> N22 + N307 --> N300 + N307 --> N306 + N308 --> N54 + N308 --> N22 + N308 --> N300 + N308 --> N306 + N308 --> N301 + N309 --> N54 + N309 --> N22 + N309 --> N300 + N309 --> N306 + N309 --> N301 + N310 --> N300 + N310 --> N301 + N311 --> N300 + N311 --> N301 + N312 --> N313 + N312 --> N314 + N315 --> N313 + N316 --> N313 + N317 --> N201 + N317 --> N318 + N317 --> N10 + N317 --> N313 + N319 --> N313 + N320 --> N314 + N321 --> N201 + N321 --> N318 + N321 --> N10 + N322 --> N313 + N322 --> N323 + N322 --> N314 + N324 --> N313 + N323 --> N10 + N314 --> N313 + N325 --> N313 N326 --> N327 - N326 --> N35 - N326 --> N175 - N326 --> N130 - N328 --> N329 - N328 --> N330 - N328 --> N276 - N328 --> N331 - N332 --> N333 - N332 --> N334 - N335 --> N336 - N335 --> N130 - N337 --> N9 - N337 --> N174 - N337 --> N10 - N337 --> N338 - N337 --> N221 - N339 --> N9 - N339 --> N174 - N339 --> N10 - N339 --> N14 - N340 --> N341 - N340 --> N238 + N326 --> N163 + N326 --> N164 + N326 --> N165 + N326 --> N166 + N326 --> N167 + N326 --> N328 + N329 --> N54 + N330 --> N10 + N330 --> N331 + N330 --> N332 + N330 --> N333 + N330 --> N334 + N330 --> N335 + N336 --> N313 + N337 --> N313 + N337 --> N323 + N337 --> N314 + N338 --> N318 + N338 --> N339 + N338 --> N300 + N338 --> N340 + N341 --> N339 + N341 --> N300 + N341 --> N340 + N342 --> N9 + N342 --> N318 N342 --> N343 - N342 --> N143 - N342 --> N152 - N342 --> N130 + N342 --> N10 + N342 --> N14 + N342 --> N339 + N342 --> N300 + N342 --> N340 + N344 --> N9 + N344 --> N10 + N344 --> N300 N344 --> N345 + N346 --> N300 N346 --> N347 N346 --> N348 - N349 --> N93 - N349 --> N350 - N351 --> N352 - N351 --> N68 - N353 --> N354 - N353 --> N355 - N353 --> N66 - N353 --> N67 - N356 --> N357 - N356 --> N68 - N358 --> N359 - N358 --> N360 - N358 --> N68 - N358 --> N109 - N358 --> N361 - N358 --> N152 - N358 --> N362 - N358 --> N363 - N364 --> N10 - N364 --> N365 - N364 --> N33 - N364 --> N68 - N364 --> N362 - N364 --> N363 - N364 --> N130 - N366 --> N68 - N367 --> N345 - N367 --> N68 + N346 --> N349 + N346 --> N350 + N346 --> N351 + N346 --> N352 + N346 --> N353 + N346 --> N354 + N346 --> N355 + N346 --> N356 + N346 --> N357 + N346 --> N358 + N359 --> N339 + N359 --> N300 + N359 --> N360 + N359 --> N301 + N359 --> N352 + N361 --> N339 + N361 --> N300 + N361 --> N360 + N362 --> N339 + N362 --> N300 + N362 --> N306 + N362 --> N347 + N362 --> N360 + N362 --> N301 + N362 --> N348 + N362 --> N349 + N362 --> N350 + N362 --> N351 + N362 --> N352 + N362 --> N353 + N362 --> N354 + N362 --> N355 + N362 --> N356 + N362 --> N357 + N362 --> N358 + N363 --> N318 + N363 --> N10 + N364 --> N300 + N364 --> N306 + N364 --> N347 + N364 --> N360 + N364 --> N301 + N364 --> N348 + N364 --> N349 + N364 --> N350 + N364 --> N351 + N364 --> N352 + N364 --> N353 + N364 --> N354 + N364 --> N355 + N364 --> N356 + N364 --> N357 + N364 --> N358 + N365 --> N300 + N365 --> N306 + N365 --> N347 + N365 --> N360 + N365 --> N301 + N365 --> N348 + N365 --> N349 + N365 --> N350 + N365 --> N351 + N365 --> N352 + N365 --> N353 + N365 --> N354 + N365 --> N355 + N365 --> N356 + N365 --> N357 + N365 --> N358 + N366 --> N318 + N366 --> N10 + N367 --> N318 + N367 --> N10 + N367 --> N300 + N367 --> N360 + N368 --> N339 + N368 --> N300 + N368 --> N306 + N368 --> N347 + N368 --> N360 + N368 --> N301 + N368 --> N348 + N368 --> N349 N368 --> N350 - N368 --> N369 + N368 --> N351 + N368 --> N352 + N368 --> N353 + N368 --> N354 N368 --> N355 - N368 --> N370 - N368 --> N67 - N368 --> N68 - N368 --> N98 - N368 --> N371 - N368 --> N362 - N372 --> N31 - N372 --> N85 - N372 --> N68 - N373 --> N30 - N373 --> N31 - N374 --> N350 - N374 --> N369 - N374 --> N375 - N374 --> N67 - N374 --> N68 - N374 --> N98 - N374 --> N371 - N376 --> N68 - N377 --> N378 - N377 --> N379 - N377 --> N350 - N377 --> N369 - N377 --> N380 - N377 --> N66 - N377 --> N67 - N377 --> N68 - N377 --> N98 - N377 --> N371 - N381 --> N382 - N381 --> N350 - N381 --> N383 - N381 --> N384 - N381 --> N66 - N381 --> N67 - N381 --> N68 - N381 --> N385 - N386 --> N35 - N387 --> N355 - N387 --> N68 - N387 --> N46 + N368 --> N356 + N368 --> N357 + N368 --> N358 + N369 --> N300 + N369 --> N340 + N370 --> N339 + N370 --> N371 + N370 --> N300 + N370 --> N340 + N370 --> N301 + N370 --> N349 + N372 --> N339 + N372 --> N371 + N372 --> N300 + N372 --> N340 + N372 --> N301 + N372 --> N350 + N373 --> N300 + N373 --> N374 + N375 --> N318 + N375 --> N10 + N375 --> N300 + N375 --> N358 + N376 --> N10 + N376 --> N377 + N378 --> N9 + N378 --> N10 + N378 --> N300 + N379 --> N10 + N379 --> N380 + N379 --> N300 + N381 --> N10 + N381 --> N380 + N381 --> N300 + N382 --> N10 + N382 --> N380 + N382 --> N300 + N383 --> N300 + N383 --> N356 + N384 --> N54 + N384 --> N300 + N384 --> N340 + N385 --> N300 + N385 --> N356 + N386 --> N300 N387 --> N388 N387 --> N389 - N390 --> N68 - N391 --> N35 - N391 --> N31 - N391 --> N392 - N393 --> N68 - N393 --> N394 - N395 --> N68 - N396 --> N357 - N396 --> N68 - N397 --> N382 - N397 --> N355 - N397 --> N398 - N397 --> N67 - N397 --> N68 - N397 --> N46 - N397 --> N385 - N399 --> N31 - N400 --> N355 - N400 --> N401 - N402 --> N35 - N402 --> N31 - N402 --> N403 - N402 --> N49 - N402 --> N350 - N402 --> N355 - N402 --> N404 - N402 --> N405 - N402 --> N67 - N402 --> N68 - N406 --> N407 - N406 --> N46 - N406 --> N408 - N406 --> N409 - N406 --> N402 - N410 --> N411 - N410 --> N68 - N410 --> N46 - N410 --> N412 - N410 --> N413 - N410 --> N414 - N410 --> N415 - N416 --> N355 - N416 --> N68 - N416 --> N46 - N417 --> N33 + N387 --> N390 + N387 --> N391 + N387 --> N54 + N387 --> N392 + N387 --> N192 + N387 --> N180 + N387 --> N295 + N387 --> N193 + N387 --> N393 + N387 --> N194 + N387 --> N195 + N387 --> N394 + N387 --> N300 + N387 --> N395 + N387 --> N357 + N396 --> N397 + N396 --> N300 + N398 --> N318 + N399 --> N300 + N399 --> N400 + N401 --> N402 + N401 --> N191 + N401 --> N192 + N401 --> N180 + N401 --> N193 + N401 --> N194 + N401 --> N195 + N401 --> N300 + N403 --> N300 + N403 --> N301 + N404 --> N201 + N404 --> N318 + N404 --> N343 + N404 --> N10 + N404 --> N295 + N404 --> N300 + N405 --> N186 + N405 --> N202 + N406 --> N9 + N406 --> N10 + N406 --> N300 + N406 --> N345 + N407 --> N300 + N407 --> N345 + N408 --> N54 + N408 --> N300 + N409 --> N201 + N409 --> N9 + N409 --> N343 + N409 --> N10 + N409 --> N410 + N409 --> N300 + N411 --> N412 + N411 --> N300 + N413 --> N54 + N413 --> N414 + N415 --> N300 + N415 --> N416 + N417 --> N300 N417 --> N418 - N417 --> N419 - N417 --> N420 - N417 --> N369 - N417 --> N355 - N417 --> N421 - N417 --> N422 - N417 --> N423 - N417 --> N67 - N417 --> N68 - N417 --> N424 - N417 --> N371 + N419 --> N300 + N420 --> N318 + N420 --> N343 + N420 --> N10 + N420 --> N300 + N420 --> N421 + N422 --> N300 + N422 --> N423 + N422 --> N352 + N424 --> N300 N425 --> N426 - N425 --> N68 - N427 --> N205 - N427 --> N142 - N428 --> N205 - N428 --> N142 - N429 --> N205 - N429 --> N142 - N430 --> N205 - N430 --> N142 - N431 --> N35 - N431 --> N205 - N431 --> N90 - N431 --> N142 - N432 --> N35 - N432 --> N33 - N432 --> N205 - N432 --> N90 - N433 --> N35 - N433 --> N33 - N433 --> N205 - N433 --> N90 - N433 --> N142 - N434 --> N35 - N434 --> N33 - N434 --> N205 - N434 --> N90 - N434 --> N142 - N435 --> N205 - N435 --> N142 - N436 --> N205 - N436 --> N142 - N437 --> N438 - N437 --> N439 - N440 --> N438 - N441 --> N438 + N425 --> N300 + N425 --> N427 + N425 --> N301 + N425 --> N428 + N429 --> N300 + N429 --> N427 + N430 --> N300 + N430 --> N427 + N431 --> N318 + N431 --> N432 + N431 --> N333 + N431 --> N300 + N431 --> N358 + N433 --> N196 + N433 --> N300 + N433 --> N434 + N435 --> N436 + N435 --> N437 + N435 --> N438 + N435 --> N300 + N435 --> N306 + N435 --> N439 + N440 --> N300 + N440 --> N441 + N442 --> N300 N442 --> N443 - N442 --> N174 - N442 --> N10 - N442 --> N438 - N444 --> N438 - N445 --> N439 - N446 --> N443 - N446 --> N174 - N446 --> N10 - N447 --> N438 + N442 --> N352 + N442 --> N356 + N442 --> N357 + N444 --> N300 + N444 --> N357 + N445 --> N9 + N445 --> N318 + N445 --> N426 + N445 --> N300 + N445 --> N446 + N447 --> N343 + N447 --> N10 N447 --> N448 - N447 --> N439 - N449 --> N438 - N448 --> N10 - N439 --> N438 - N450 --> N438 + N447 --> N426 + N447 --> N91 + N447 --> N191 + N449 --> N426 + N449 --> N450 N451 --> N452 - N451 --> N453 - N451 --> N454 - N451 --> N455 - N451 --> N456 - N451 --> N457 - N451 --> N458 - N459 --> N35 - N460 --> N10 - N460 --> N461 - N460 --> N462 - N460 --> N247 - N460 --> N463 - N460 --> N464 - N465 --> N438 - N466 --> N438 + N451 --> N306 + N451 --> N52 + N453 --> N436 + N453 --> N448 + N453 --> N91 + N453 --> N454 + N453 --> N455 + N453 --> N456 + N457 --> N52 + N458 --> N54 + N458 --> N52 + N459 --> N52 + N460 --> N52 + N461 --> N52 + N461 --> N462 + N463 --> N52 + N464 --> N465 + N462 --> N54 + N462 --> N52 N466 --> N448 - N466 --> N439 - N467 --> N174 - N467 --> N140 - N467 --> N205 - N467 --> N141 - N468 --> N140 - N468 --> N205 - N468 --> N141 - N469 --> N9 - N469 --> N174 - N469 --> N92 - N469 --> N10 - N469 --> N14 - N469 --> N140 - N469 --> N205 - N469 --> N141 - N470 --> N9 + N466 --> N22 + N466 --> N306 + N466 --> N52 + N467 --> N52 + N468 --> N79 + N468 --> N52 + N469 --> N81 + N469 --> N52 + N470 --> N343 N470 --> N10 - N470 --> N205 - N470 --> N209 - N471 --> N205 - N471 --> N198 - N471 --> N237 - N471 --> N274 + N470 --> N436 + N471 --> N436 N471 --> N472 - N471 --> N322 - N471 --> N228 - N471 --> N334 - N471 --> N229 - N471 --> N238 - N471 --> N130 - N471 --> N155 - N471 --> N232 - N473 --> N140 - N473 --> N205 - N473 --> N212 - N473 --> N142 - N473 --> N228 - N474 --> N140 - N474 --> N205 - N474 --> N212 - N475 --> N140 - N475 --> N205 - N475 --> N90 - N475 --> N198 - N475 --> N212 - N475 --> N142 - N475 --> N237 - N475 --> N274 - N475 --> N472 - N475 --> N322 - N475 --> N228 - N475 --> N334 - N475 --> N229 - N475 --> N238 - N475 --> N130 - N475 --> N155 - N475 --> N232 - N476 --> N174 - N476 --> N10 - N477 --> N205 - N477 --> N90 - N477 --> N198 - N477 --> N212 - N477 --> N142 - N477 --> N237 - N477 --> N274 - N477 --> N472 - N477 --> N322 - N477 --> N228 - N477 --> N334 - N477 --> N229 - N477 --> N238 - N477 --> N130 - N477 --> N155 - N477 --> N232 - N478 --> N205 - N478 --> N90 - N478 --> N198 - N478 --> N212 - N478 --> N142 - N478 --> N237 - N478 --> N274 - N478 --> N472 - N478 --> N322 - N478 --> N228 - N478 --> N334 - N478 --> N229 - N478 --> N238 - N478 --> N130 - N478 --> N155 - N478 --> N232 - N479 --> N174 - N479 --> N10 - N480 --> N174 - N480 --> N10 - N480 --> N205 - N480 --> N212 - N481 --> N140 - N481 --> N205 - N481 --> N90 - N481 --> N198 - N481 --> N212 - N481 --> N142 - N481 --> N237 - N481 --> N274 - N481 --> N472 - N481 --> N322 - N481 --> N228 - N481 --> N334 - N481 --> N229 - N481 --> N238 - N481 --> N130 - N481 --> N155 - N481 --> N232 - N482 --> N205 - N482 --> N141 - N483 --> N140 + N471 --> N473 + N474 --> N436 + N474 --> N475 + N474 --> N476 + N477 --> N436 + N477 --> N478 + N477 --> N479 + N477 --> N480 + N481 --> N436 + N481 --> N482 + N481 --> N356 + N483 --> N436 N483 --> N484 - N483 --> N205 - N483 --> N141 - N483 --> N142 - N483 --> N274 - N485 --> N140 - N485 --> N484 - N485 --> N205 - N485 --> N141 - N485 --> N142 - N485 --> N472 - N486 --> N205 - N486 --> N144 - N487 --> N174 - N487 --> N10 - N487 --> N205 - N487 --> N232 - N488 --> N10 - N488 --> N240 - N489 --> N9 - N489 --> N10 - N489 --> N205 - N490 --> N10 + N483 --> N485 + N486 --> N436 + N486 --> N487 + N486 --> N423 + N488 --> N436 + N488 --> N489 + N488 --> N54 + N488 --> N196 + N488 --> N339 + N488 --> N306 + N488 --> N340 + N488 --> N301 + N488 --> N434 + N488 --> N423 + N488 --> N374 + N490 --> N436 N490 --> N491 - N490 --> N205 - N492 --> N10 - N492 --> N491 - N492 --> N205 - N493 --> N10 - N493 --> N491 - N493 --> N205 - N494 --> N205 - N494 --> N130 - N495 --> N35 - N495 --> N205 - N495 --> N141 - N496 --> N205 - N496 --> N130 - N497 --> N205 - N498 --> N499 - N498 --> N500 - N498 --> N501 - N498 --> N221 - N498 --> N35 - N498 --> N249 - N498 --> N259 - N498 --> N265 - N498 --> N175 - N498 --> N272 - N498 --> N279 - N498 --> N282 - N498 --> N285 - N498 --> N502 - N498 --> N205 - N498 --> N199 - N498 --> N155 - N503 --> N310 - N503 --> N205 - N504 --> N174 - N505 --> N205 - N505 --> N231 - N506 --> N343 - N506 --> N95 - N506 --> N259 - N506 --> N265 - N506 --> N272 - N506 --> N282 - N506 --> N285 - N506 --> N205 - N507 --> N205 - N507 --> N142 - N508 --> N443 - N508 --> N174 - N508 --> N92 - N508 --> N10 - N508 --> N175 - N508 --> N205 - N509 --> N9 - N509 --> N10 - N509 --> N205 - N509 --> N209 - N510 --> N205 - N510 --> N209 - N511 --> N35 - N511 --> N205 - N512 --> N443 - N512 --> N9 - N512 --> N92 - N512 --> N10 - N512 --> N329 - N512 --> N205 - N513 --> N514 - N513 --> N205 - N515 --> N35 - N515 --> N516 - N517 --> N205 - N517 --> N28 - N518 --> N205 - N518 --> N519 - N520 --> N205 - N521 --> N174 - N521 --> N92 - N521 --> N10 - N521 --> N205 - N521 --> N200 - N522 --> N205 - N522 --> N136 - N522 --> N228 - N523 --> N205 - N524 --> N224 - N524 --> N205 - N524 --> N234 - N524 --> N142 - N524 --> N202 - N525 --> N205 - N525 --> N234 - N526 --> N205 - N526 --> N234 - N527 --> N174 - N527 --> N295 - N527 --> N247 - N527 --> N205 - N527 --> N232 - N528 --> N139 - N528 --> N205 - N528 --> N143 - N529 --> N106 - N529 --> N359 - N529 --> N360 - N529 --> N205 - N529 --> N90 - N529 --> N361 - N530 --> N205 - N530 --> N227 - N530 --> N228 - N530 --> N130 - N530 --> N155 - N531 --> N205 - N531 --> N155 - N532 --> N9 - N532 --> N174 - N532 --> N224 - N532 --> N205 - N532 --> N533 - N534 --> N353 - N534 --> N399 - N534 --> N397 - N534 --> N425 - N534 --> N368 - N534 --> N356 - N534 --> N351 - N534 --> N387 - N534 --> N396 - N534 --> N376 - N534 --> N417 - N534 --> N390 - N534 --> N381 - N534 --> N377 - N534 --> N374 - N534 --> N373 - N534 --> N372 - N534 --> N416 - N534 --> N395 - N534 --> N358 - N534 --> N393 - N534 --> N400 - N534 --> N364 - N534 --> N366 - N534 --> N391 - N534 --> N367 - N535 --> N536 - N537 --> N536 - N538 --> N33 - N538 --> N539 - N538 --> N536 - N540 --> N354 - N540 --> N355 - N540 --> N541 - N540 --> N542 - N540 --> N543 - N540 --> N544 - N540 --> N545 - N540 --> N546 - N547 --> N548 - N547 --> N549 - N547 --> N550 - N547 --> N551 - N547 --> N536 - N547 --> N552 - N547 --> N553 - N554 --> N548 - N554 --> N549 - N554 --> N550 - N554 --> N551 - N554 --> N536 - N554 --> N552 - N554 --> N553 - N545 --> N551 - N545 --> N547 - N545 --> N554 - N545 --> N555 - N545 --> N556 - N545 --> N557 - N545 --> N558 - N555 --> N548 - N555 --> N549 - N555 --> N550 - N555 --> N551 - N555 --> N536 - N555 --> N552 - N555 --> N553 - N556 --> N548 - N556 --> N549 - N556 --> N550 - N556 --> N551 - N556 --> N536 - N556 --> N552 - N556 --> N553 - N557 --> N548 - N557 --> N549 - N557 --> N550 - N557 --> N551 - N557 --> N536 - N557 --> N552 - N557 --> N553 - N558 --> N548 - N558 --> N549 - N558 --> N550 - N558 --> N551 - N558 --> N536 - N558 --> N552 - N558 --> N553 - N559 --> N560 - N561 --> N23 - N561 --> N21 - N562 --> N27 - N562 --> N98 - N563 --> N35 - N563 --> N36 - N563 --> N98 - N564 --> N35 - N565 --> N35 - N565 --> N566 - N565 --> N65 - N565 --> N541 - N565 --> N385 - N567 --> N35 - N568 --> N33 - N568 --> N45 - N568 --> N542 - N568 --> N47 - N569 --> N35 - N570 --> N73 - N570 --> N561 + N490 --> N492 + N490 --> N480 + N493 --> N436 + N493 --> N494 + N493 --> N186 + N493 --> N495 + N493 --> N496 + N493 --> N497 + N493 --> N498 + N493 --> N357 + N499 --> N436 + N499 --> N500 + N499 --> N501 + N502 --> N436 + N502 --> N503 + N502 --> N504 + N502 --> N480 + N505 --> N436 + N506 --> N436 + N506 --> N507 + N506 --> N508 + N509 --> N436 + N509 --> N510 + N509 --> N511 + N512 --> N436 + N512 --> N513 + N514 --> N436 + N514 --> N515 + N480 --> N339 + N480 --> N340 + N516 --> N436 + N516 --> N517 + N516 --> N518 + N519 --> N9 + N519 --> N318 + N519 --> N295 + N519 --> N520 + N519 --> N521 + N519 --> N522 + N523 --> N436 + N523 --> N91 + N523 --> N524 + N523 --> N525 + N523 --> N526 + N523 --> N527 + N523 --> N528 + N523 --> N529 + N523 --> N530 + N523 --> N531 + N523 --> N532 + N523 --> N533 + N523 --> N534 + N523 --> N535 + N523 --> N536 + N523 --> N537 + N523 --> N538 + N523 --> N539 + N523 --> N540 + N523 --> N541 + N542 --> N347 + N542 --> N395 + N542 --> N434 + N542 --> N421 + N542 --> N543 + N542 --> N428 + N544 --> N545 + N544 --> N300 + N544 --> N301 + N546 --> N547 + N546 --> N548 + N546 --> N345 + N546 --> N549 + N550 --> N339 + N550 --> N360 + N551 --> N552 + N551 --> N360 + N553 --> N10 + N553 --> N547 + N553 --> N554 + N553 --> N555 + N553 --> N548 + N553 --> N496 + N553 --> N549 + N553 --> N556 + N549 --> N547 + N549 --> N557 + N558 --> N318 + N558 --> N391 + N559 --> N318 + N559 --> N343 + N559 --> N10 + N560 --> N426 + N561 --> N10 + N561 --> N434 + N561 --> N562 + N561 --> N443 + N561 --> N496 + N561 --> N352 + N561 --> N354 + N561 --> N356 + N561 --> N498 + N561 --> N563 + N561 --> N400 + N561 --> N358 + N564 --> N377 + N565 --> N426 + N565 --> N427 + N565 --> N301 + N565 --> N434 + N565 --> N496 + N566 --> N452 + N566 --> N339 + N566 --> N306 + N566 --> N340 + N567 --> N348 + N567 --> N355 + N568 --> N318 + N568 --> N10 + N568 --> N452 + N569 --> N10 + N569 --> N570 + N569 --> N434 + N569 --> N496 N571 --> N572 - N544 --> N19 - N573 --> N536 - N574 --> N536 - N575 --> N536 - N576 --> N536 - N577 --> N536 - N578 --> N35 - N578 --> N536 - N579 --> N98 - N580 --> N116 - N580 --> N536 - N581 --> N550 - N581 --> N582 + N573 --> N333 + N574 --> N392 + N574 --> N575 + N574 --> N576 + N577 --> N578 + N577 --> N579 + N577 --> N576 + N580 --> N581 + N580 --> N582 + N580 --> N576 + N583 --> N192 N583 --> N584 + N583 --> N576 N585 --> N586 N585 --> N587 + N585 --> N576 + N588 --> N180 N588 --> N589 - N588 --> N94 - N588 --> N586 + N588 --> N576 + N590 --> N295 N590 --> N591 - N546 --> N589 - N546 --> N350 - N592 --> N352 - N592 --> N541 - N593 --> N354 - N593 --> N355 - N593 --> N66 - N593 --> N541 - N593 --> N594 - N593 --> N552 - N595 --> N30 - N596 --> N350 - N596 --> N369 - N596 --> N370 + N590 --> N576 + N592 --> N593 + N592 --> N576 + N594 --> N193 + N594 --> N595 + N594 --> N593 + N594 --> N576 + N594 --> N496 + N594 --> N349 + N594 --> N355 N596 --> N597 - N596 --> N541 - N596 --> N98 N596 --> N598 - N599 --> N30 - N599 --> N31 - N600 --> N31 - N600 --> N403 - N600 --> N601 - N600 --> N49 - N600 --> N602 - N600 --> N541 - N600 --> N603 - N600 --> N604 - N600 --> N424 - N600 --> N51 + N596 --> N576 + N599 --> N393 + N599 --> N600 + N599 --> N576 + N601 --> N194 + N601 --> N602 + N601 --> N576 + N603 --> N195 + N603 --> N604 + N603 --> N576 + N605 --> N199 N605 --> N606 - N605 --> N350 - N605 --> N355 - N605 --> N401 - N605 --> N597 - N605 --> N541 - N605 --> N607 - N605 --> N608 - N605 --> N424 - N605 --> N598 - N609 --> N606 - N609 --> N49 - N609 --> N407 - N609 --> N405 - N609 --> N541 - N609 --> N603 + N605 --> N576 + N607 --> N202 + N607 --> N608 + N607 --> N576 + N609 --> N22 N609 --> N610 - N609 --> N409 - N609 --> N424 - N609 --> N51 - N611 --> N541 - N612 --> N33 - N612 --> N23 - N612 --> N418 - N612 --> N419 - N612 --> N420 - N612 --> N369 - N612 --> N421 - N612 --> N422 - N612 --> N423 - N612 --> N613 - N612 --> N597 - N612 --> N541 - N612 --> N614 - N612 --> N615 - N612 --> N98 - N616 --> N352 - N616 --> N541 - N616 --> N542 - N617 --> N593 - N617 --> N599 - N617 --> N592 - N617 --> N595 - N617 --> N596 + N609 --> N611 + N610 --> N9 + N610 --> N318 + N610 --> N10 + N610 --> N22 + N610 --> N611 + N611 --> N9 + N611 --> N13 + N612 --> N318 + N612 --> N10 + N613 --> N432 + N613 --> N496 + N614 --> N13 + N615 --> N616 N617 --> N618 - N617 --> N600 - N617 --> N619 - N617 --> N612 - N617 --> N616 - N617 --> N611 - N617 --> N605 + N619 --> N542 N620 --> N621 - N620 --> N94 - N622 --> N623 - N622 --> N624 - N622 --> N625 - N626 --> N627 - N626 --> N628 - N629 --> N627 - N629 --> N626 - N629 --> N630 - N629 --> N631 - N631 --> N627 - N631 --> N628 - N630 --> N627 - N630 --> N628 - N632 --> N633 - N632 --> N631 + N620 --> N349 + N622 --> N9 + N622 --> N318 + N622 --> N10 + N622 --> N13 + N622 --> N14 + N623 --> N54 + N623 --> N443 + N623 --> N496 + N623 --> N356 + N624 --> N625 + N624 --> N626 + N627 --> N628 + N627 --> N397 + N627 --> N629 + N627 --> N340 + N627 --> N356 + N627 --> N630 + N631 --> N632 + N631 --> N633 N634 --> N635 - N634 --> N453 - N634 --> N454 - N634 --> N455 - N634 --> N456 - N634 --> N457 N634 --> N636 - N637 --> N625 - N637 --> N627 - N637 --> N638 - N637 --> N639 - N640 --> N641 - N642 --> N327 - N643 --> N327 - N644 --> N35 - N645 --> N40 - N645 --> N265 - N645 --> N646 - N647 --> N10 - N647 --> N39 - N647 --> N327 - N648 --> N10 - N648 --> N649 - N648 --> N150 - N650 --> N327 + N637 --> N318 + N637 --> N10 + N637 --> N22 + N637 --> N434 + N637 --> N443 + N637 --> N496 + N637 --> N352 + N637 --> N354 + N637 --> N356 + N637 --> N498 + N637 --> N400 + N638 --> N639 + N638 --> N351 + N640 --> N628 + N640 --> N295 + N640 --> N434 + N641 --> N9 + N641 --> N318 + N641 --> N10 + N641 --> N14 + N641 --> N295 + N641 --> N522 + N642 --> N318 + N642 --> N10 + N642 --> N193 + N642 --> N194 + N642 --> N427 + N642 --> N443 + N642 --> N496 + N643 --> N175 + N643 --> N54 + N643 --> N295 + N643 --> N356 + N644 --> N410 + N644 --> N645 + N644 --> N597 + N644 --> N646 + N647 --> N648 + N647 --> N353 + N649 --> N650 + N649 --> N356 + N651 --> N9 + N651 --> N318 N651 --> N10 - N651 --> N649 - N651 --> N150 - N652 --> N10 - N652 --> N649 - N652 --> N150 + N651 --> N652 + N651 --> N391 + N653 --> N9 + N653 --> N318 N653 --> N10 - N653 --> N95 - N653 --> N259 - N653 --> N265 - N653 --> N272 - N653 --> N282 - N653 --> N285 - N653 --> N139 - N653 --> N33 - N653 --> N654 - N655 --> N288 - N656 --> N443 - N656 --> N9 - N656 --> N14 + N653 --> N14 + N654 --> N655 + N654 --> N355 + N656 --> N402 + N656 --> N434 + N656 --> N496 + N656 --> N356 N657 --> N658 - N657 --> N659 - N657 --> N660 - N657 --> N661 + N659 --> N660 + N659 --> N661 + N662 --> N410 N662 --> N663 - N662 --> N661 - N664 --> N663 - N664 --> N661 - N664 --> N657 + N662 --> N202 + N662 --> N664 + N665 --> N96 N665 --> N666 - N665 --> N667 + N667 --> N25 + N667 --> N26 + N667 --> N98 + N667 --> N618 + N668 --> N669 N668 --> N666 - N669 --> N666 + N670 --> N437 + N670 --> N438 N670 --> N666 - N671 --> N666 - N672 --> N673 - N672 --> N666 - N674 --> N673 - N674 --> N665 - N675 --> N673 + N670 --> N456 + N670 --> N439 + N670 --> N496 + N670 --> N671 + N670 --> N672 + N673 --> N10 + N673 --> N674 + N673 --> N22 + N673 --> N666 + N673 --> N671 + N673 --> N672 + N673 --> N356 N675 --> N666 - N675 --> N668 - N676 --> N673 + N676 --> N658 N676 --> N666 - N677 --> N673 + N677 --> N94 + N677 --> N103 + N677 --> N26 + N677 --> N104 + N677 --> N618 + N677 --> N666 + N677 --> N52 + N677 --> N678 N677 --> N671 - N678 --> N673 - N678 --> N666 - N679 --> N673 + N679 --> N108 + N679 --> N680 N679 --> N666 - N679 --> N667 - N680 --> N673 - N680 --> N666 - N681 --> N672 - N681 --> N674 - N681 --> N675 - N681 --> N676 - N681 --> N677 - N681 --> N678 - N681 --> N679 - N681 --> N680 - N681 --> N682 - N681 --> N683 - N681 --> N684 - N681 --> N685 - N681 --> N686 - N681 --> N687 - N681 --> N688 - N681 --> N689 - N681 --> N690 - N682 --> N673 + N681 --> N101 + N681 --> N108 + N682 --> N94 + N682 --> N103 + N682 --> N683 + N682 --> N618 N682 --> N666 - N682 --> N691 - N684 --> N673 + N682 --> N52 + N682 --> N678 N684 --> N666 - N685 --> N673 + N685 --> N287 + N685 --> N686 + N685 --> N94 + N685 --> N103 + N685 --> N687 + N685 --> N98 + N685 --> N618 N685 --> N666 - N686 --> N673 - N686 --> N666 - N687 --> N673 - N687 --> N666 - N687 --> N692 - N687 --> N693 - N688 --> N673 + N685 --> N52 + N685 --> N678 + N688 --> N689 + N688 --> N94 + N688 --> N690 + N688 --> N691 + N688 --> N98 + N688 --> N618 N688 --> N666 - N689 --> N673 - N689 --> N666 - N690 --> N673 - N690 --> N666 - N683 --> N673 - N683 --> N666 - N694 --> N673 - N694 --> N685 - N695 --> N666 - N691 --> N666 - N667 --> N666 - N696 --> N666 - N697 --> N667 + N688 --> N60 + N692 --> N54 + N693 --> N26 + N693 --> N666 + N693 --> N694 + N693 --> N695 + N693 --> N696 + N697 --> N116 + N697 --> N666 + N698 --> N54 + N698 --> N108 N698 --> N699 + N700 --> N116 N700 --> N666 - N701 --> N702 - N701 --> N699 - N703 --> N704 - N705 --> N706 - N705 --> N702 - N707 --> N702 - N707 --> N708 - N709 --> N702 - N710 --> N702 - N711 --> N702 - N712 --> N702 - N712 --> N699 - N712 --> N713 - N714 --> N715 + N700 --> N701 + N702 --> N110 + N702 --> N111 + N702 --> N112 + N702 --> N113 + N702 --> N703 + N702 --> N115 + N702 --> N666 + N702 --> N704 + N702 --> N705 + N702 --> N118 + N702 --> N119 + N702 --> N120 + N706 --> N666 + N707 --> N669 + N707 --> N666 + N708 --> N689 + N708 --> N26 + N708 --> N709 + N708 --> N618 + N708 --> N666 + N708 --> N694 + N708 --> N60 + N710 --> N108 + N710 --> N110 + N710 --> N111 + N710 --> N112 + N710 --> N113 + N710 --> N26 + N710 --> N114 + N710 --> N115 + N710 --> N116 + N710 --> N666 + N710 --> N694 + N710 --> N711 + N710 --> N118 + N710 --> N119 + N710 --> N120 + N712 --> N26 + N712 --> N123 + N713 --> N54 + N713 --> N108 + N713 --> N110 + N713 --> N113 + N713 --> N94 + N713 --> N26 + N713 --> N714 + N713 --> N115 + N713 --> N128 + N713 --> N618 + N713 --> N666 + N715 --> N127 + N715 --> N115 + N715 --> N694 + N715 --> N711 + N715 --> N130 + N715 --> N120 + N715 --> N713 + N716 --> N717 + N716 --> N115 N716 --> N666 - N716 --> N699 - N717 --> N666 - N717 --> N718 - N719 --> N666 - N693 --> N666 - N693 --> N720 - N693 --> N721 + N716 --> N694 + N716 --> N704 + N716 --> N705 + N716 --> N718 + N716 --> N120 + N716 --> N719 + N720 --> N26 + N720 --> N116 + N720 --> N666 + N720 --> N694 + N721 --> N22 + N721 --> N133 + N721 --> N134 + N721 --> N135 + N721 --> N103 + N721 --> N26 + N721 --> N136 + N721 --> N137 + N721 --> N138 + N721 --> N618 N721 --> N666 + N721 --> N118 + N721 --> N678 N722 --> N723 - N724 --> N666 - N725 --> N726 - N725 --> N727 - N727 --> N666 - N728 --> N666 - N692 --> N666 - N720 --> N666 - N729 --> N730 - N731 --> N666 - N731 --> N718 - N732 --> N726 - N733 --> N443 - N733 --> N150 - N734 --> N38 - N735 --> N378 - N736 --> N150 - N737 --> N150 - N738 --> N739 - N738 --> N740 - N738 --> N735 - N741 --> N175 + N722 --> N666 + N724 --> N49 + N724 --> N725 + N725 --> N48 + N726 --> N727 + N726 --> N725 + N728 --> N51 + N728 --> N416 + N729 --> N101 + N729 --> N108 + N730 --> N22 + N730 --> N108 + N731 --> N54 + N731 --> N55 + N731 --> N416 + N732 --> N285 + N732 --> N183 + N732 --> N179 + N732 --> N733 + N732 --> N734 + N732 --> N101 + N732 --> N108 + N735 --> N54 + N736 --> N22 + N736 --> N63 + N736 --> N694 + N736 --> N64 + N737 --> N113 + N737 --> N738 + N737 --> N119 + N739 --> N285 + N739 --> N183 + N739 --> N179 + N740 --> N285 + N740 --> N183 + N740 --> N179 + N740 --> N54 + N740 --> N733 + N740 --> N734 + N740 --> N108 + N741 --> N285 + N741 --> N183 + N741 --> N179 + N741 --> N733 + N741 --> N22 + N741 --> N734 + N741 --> N108 N741 --> N742 - N740 --> N743 - N740 --> N744 - N740 --> N741 - N745 --> N97 - N745 --> N746 - N745 --> N747 - N748 --> N17 - N748 --> N746 - N748 --> N747 - N748 --> N749 - N747 --> N750 - N747 --> N746 - N747 --> N751 - N752 --> N746 - N752 --> N751 - N753 --> N35 - N753 --> N97 - N753 --> N746 - N753 --> N754 - N755 --> N746 - N755 --> N756 - N755 --> N751 + N743 --> N742 + N744 --> N54 + N744 --> N745 + N746 --> N747 + N746 --> N416 + N748 --> N54 + N749 --> N54 + N749 --> N17 + N749 --> N101 + N749 --> N750 + N749 --> N751 + N749 --> N59 + N749 --> N98 + N749 --> N618 + N749 --> N666 + N752 --> N54 + N752 --> N17 + N752 --> N108 + N753 --> N54 + N753 --> N17 + N754 --> N54 + N755 --> N67 + N755 --> N724 + N756 --> N108 + N756 --> N694 + N757 --> N694 N757 --> N758 - N759 --> N760 - N759 --> N761 - N759 --> N762 - N759 --> N746 - N763 --> N764 - N763 --> N765 - N766 --> N746 - N767 --> N768 - N769 --> N39 - N769 --> N40 - N769 --> N35 - N769 --> N41 - N770 --> N39 - N770 --> N40 - N771 --> N768 - N771 --> N772 - N773 --> N39 - N773 --> N40 - N773 --> N378 - N774 --> N768 - N774 --> N775 - N775 --> N33 - N775 --> N776 - N777 --> N139 - N777 --> N768 - N778 --> N746 - N778 --> N768 - N779 --> N746 - N779 --> N780 - N781 --> N139 - N781 --> N746 - N781 --> N780 + N757 --> N759 + N760 --> N761 + N760 --> N762 + N763 --> N618 + N764 --> N765 + N764 --> N762 + N766 --> N108 + N766 --> N680 + N766 --> N767 + N768 --> N22 + N768 --> N108 + N769 --> N22 + N769 --> N108 + N770 --> N9 + N770 --> N10 + N770 --> N306 + N771 --> N667 + N771 --> N710 + N771 --> N708 + N771 --> N722 + N771 --> N677 + N771 --> N668 + N771 --> N665 + N771 --> N693 + N771 --> N707 + N771 --> N684 + N771 --> N721 + N771 --> N697 + N771 --> N688 + N771 --> N685 + N771 --> N682 + N771 --> N681 + N771 --> N679 + N771 --> N720 + N771 --> N706 + N771 --> N670 + N771 --> N700 + N771 --> N712 + N771 --> N673 + N771 --> N675 + N771 --> N698 + N771 --> N676 + N772 --> N70 + N773 --> N452 + N773 --> N774 + N773 --> N775 + N776 --> N17 + N776 --> N774 + N776 --> N775 + N776 --> N777 + N775 --> N778 + N775 --> N774 + N775 --> N779 + N780 --> N774 + N780 --> N779 + N781 --> N54 + N781 --> N452 + N781 --> N774 N781 --> N782 - N783 --> N35 - N783 --> N33 - N784 --> N746 - N784 --> N751 - N785 --> N746 - N785 --> N751 - N786 --> N746 - N787 --> N35 - N787 --> N746 - N787 --> N752 - N787 --> N754 - N787 --> N751 - N788 --> N35 - N788 --> N746 - N788 --> N751 - N789 --> N790 - N789 --> N746 - N789 --> N780 - N789 --> N749 - N791 --> N39 - N791 --> N40 - N791 --> N35 - N791 --> N41 - N792 --> N39 - N792 --> N40 - N792 --> N35 - N792 --> N41 - N793 --> N746 - N793 --> N749 - N794 --> N749 - N795 --> N746 + N783 --> N774 + N783 --> N784 + N783 --> N779 + N785 --> N786 + N787 --> N788 + N787 --> N789 + N787 --> N790 + N787 --> N774 + N791 --> N792 + N791 --> N793 + N794 --> N774 N795 --> N796 - N795 --> N797 - N795 --> N798 - N799 --> N761 + N797 --> N183 + N797 --> N179 + N797 --> N54 + N797 --> N733 + N798 --> N183 + N798 --> N179 + N799 --> N796 N799 --> N800 - N799 --> N746 - N799 --> N751 - N801 --> N116 - N801 --> N35 - N802 --> N38 - N802 --> N39 - N802 --> N40 - N803 --> N38 - N803 --> N39 - N803 --> N40 - N803 --> N35 - N803 --> N41 - N803 --> N42 - N803 --> N746 - N803 --> N749 - N804 --> N38 - N804 --> N776 - N805 --> N39 - N805 --> N776 - N806 --> N224 - N806 --> N776 - N807 --> N40 - N807 --> N776 - N808 --> N35 - N808 --> N809 - N808 --> N776 - N810 --> N41 - N810 --> N776 - N811 --> N33 - N811 --> N776 - N812 --> N42 - N812 --> N776 - N813 --> N780 - N814 --> N776 - N814 --> N768 - N815 --> N776 - N816 --> N224 - N816 --> N817 - N816 --> N746 - N816 --> N818 - N816 --> N749 - N819 --> N40 - N819 --> N35 - N819 --> N746 - N820 --> N790 - N821 --> N790 - N821 --> N746 - N821 --> N822 - N821 --> N752 - N821 --> N823 - N821 --> N824 - N821 --> N825 - N821 --> N826 - N821 --> N827 - N828 --> N790 - N828 --> N746 - N828 --> N822 - N828 --> N749 - N828 --> N752 - N829 --> N224 - N829 --> N830 - N831 --> N832 - N831 --> N833 - N831 --> N834 - N831 --> N835 - N831 --> N836 - N831 --> N837 - N831 --> N838 - N831 --> N839 - N831 --> N840 - N831 --> N841 - N831 --> N842 - N831 --> N843 - N831 --> N844 - N831 --> N845 - N831 --> N846 - N831 --> N847 - N831 --> N848 - N831 --> N849 - N831 --> N850 - N831 --> N851 - N831 --> N852 - N831 --> N853 - N831 --> N854 - N831 --> N855 - N831 --> N856 - N831 --> N857 - N831 --> N858 - N831 --> N859 - N831 --> N860 - N831 --> N861 - N831 --> N862 - N831 --> N863 - N831 --> N864 - N831 --> N865 - N831 --> N866 - N867 --> N746 - N867 --> N822 - N867 --> N825 - N867 --> N751 - N867 --> N816 - N867 --> N829 - N868 --> N869 - N868 --> N825 - N868 --> N870 - N868 --> N871 - N871 --> N35 - N870 --> N35 - N827 --> N35 - N827 --> N33 - N872 --> N35 - N872 --> N746 - N873 --> N224 - N873 --> N97 - N873 --> N817 - N873 --> N790 - N873 --> N835 - N873 --> N836 - N873 --> N843 - N873 --> N850 - N873 --> N851 - N873 --> N855 - N873 --> N746 - N873 --> N818 - N874 --> N97 - N874 --> N746 - N874 --> N822 - N874 --> N825 - N874 --> N751 - N874 --> N875 - N874 --> N829 - N874 --> N873 - N876 --> N746 - N877 --> N746 - N877 --> N749 - N877 --> N754 - N877 --> N769 - N877 --> N785 - N877 --> N878 - N877 --> N879 - N880 --> N845 - N880 --> N881 - N880 --> N746 - N880 --> N749 - N880 --> N754 - N880 --> N769 - N880 --> N784 - N880 --> N785 - N882 --> N40 - N882 --> N746 - N883 --> N746 - N883 --> N749 - N883 --> N769 - N883 --> N785 - N884 --> N885 - N884 --> N859 - N884 --> N862 - N884 --> N865 - N884 --> N746 - N884 --> N822 - N884 --> N749 - N878 --> N746 - N878 --> N747 - N878 --> N769 - N878 --> N886 - N887 --> N835 - N887 --> N888 - N887 --> N848 - N887 --> N851 - N887 --> N864 - N887 --> N746 - N887 --> N749 - N879 --> N769 - N889 --> N746 - N889 --> N749 - N889 --> N769 - N889 --> N785 - N889 --> N878 - N889 --> N879 - N890 --> N769 - N891 --> N768 - N891 --> N892 - N892 --> N39 - N892 --> N40 - N893 --> N825 - N893 --> N894 - N893 --> N870 - N893 --> N871 - N895 --> N35 - N895 --> N746 - N895 --> N822 - N895 --> N825 - N896 --> N881 - N896 --> N746 - N896 --> N822 - N896 --> N749 - N824 --> N38 - N824 --> N39 - N824 --> N40 - N824 --> N41 - N824 --> N516 - N824 --> N897 - N824 --> N33 - N824 --> N42 - N824 --> N746 - N824 --> N898 - N824 --> N749 - N824 --> N785 - N824 --> N899 - N824 --> N803 - N824 --> N900 - N900 --> N38 - N900 --> N39 - N900 --> N40 - N900 --> N41 - N900 --> N897 - N900 --> N42 - N900 --> N746 - N900 --> N898 - N900 --> N747 - N900 --> N749 - N901 --> N761 - N902 --> N785 - N903 --> N749 - N826 --> N35 - N904 --> N39 - N904 --> N40 - N905 --> N35 - N906 --> N768 - N907 --> N758 - N908 --> N758 - N909 --> N746 - N909 --> N751 - N910 --> N746 - N911 --> N35 - N911 --> N746 - N911 --> N910 - N756 --> N746 - N756 --> N780 - N751 --> N912 - N751 --> N913 - N914 --> N746 - N822 --> N746 - N822 --> N780 - N822 --> N749 - N822 --> N830 - N822 --> N751 - N915 --> N768 - N915 --> N782 - N916 --> N746 - N916 --> N751 - N917 --> N768 - N918 --> N818 - N919 --> N97 - N919 --> N746 - N919 --> N747 - N920 --> N768 - N920 --> N830 - N921 --> N790 - N921 --> N746 - N921 --> N822 + N801 --> N183 + N801 --> N179 + N801 --> N287 + N802 --> N796 + N802 --> N803 + N803 --> N22 + N803 --> N804 + N805 --> N196 + N805 --> N796 + N806 --> N774 + N806 --> N796 + N807 --> N774 + N807 --> N808 + N809 --> N196 + N809 --> N774 + N809 --> N808 + N809 --> N810 + N811 --> N54 + N811 --> N22 + N812 --> N774 + N812 --> N779 + N813 --> N774 + N813 --> N779 + N814 --> N774 + N815 --> N54 + N815 --> N774 + N815 --> N780 + N815 --> N782 + N815 --> N779 + N816 --> N54 + N816 --> N774 + N816 --> N779 + N817 --> N818 + N817 --> N774 + N817 --> N808 + N817 --> N777 + N819 --> N183 + N819 --> N179 + N819 --> N54 + N819 --> N733 + N820 --> N183 + N820 --> N179 + N820 --> N54 + N820 --> N733 + N821 --> N774 + N821 --> N777 + N822 --> N777 + N823 --> N774 + N823 --> N824 + N823 --> N825 + N823 --> N826 + N827 --> N789 + N827 --> N828 + N827 --> N774 + N827 --> N779 + N829 --> N79 + N829 --> N54 + N830 --> N285 + N830 --> N183 + N830 --> N179 + N831 --> N285 + N831 --> N183 + N831 --> N179 + N831 --> N54 + N831 --> N733 + N831 --> N734 + N831 --> N774 + N831 --> N777 + N832 --> N285 + N832 --> N804 + N833 --> N183 + N833 --> N804 + N834 --> N426 + N834 --> N804 + N835 --> N179 + N835 --> N804 + N836 --> N54 + N836 --> N837 + N836 --> N804 + N838 --> N733 + N838 --> N804 + N839 --> N22 + N839 --> N804 + N840 --> N734 + N840 --> N804 + N841 --> N808 + N842 --> N804 + N842 --> N796 + N843 --> N804 + N844 --> N426 + N844 --> N845 + N844 --> N774 + N844 --> N846 + N844 --> N777 + N847 --> N179 + N847 --> N54 + N847 --> N774 + N848 --> N818 + N849 --> N818 + N849 --> N774 + N849 --> N850 + N849 --> N780 + N849 --> N851 + N849 --> N852 + N849 --> N853 + N849 --> N854 + N849 --> N855 + N856 --> N818 + N856 --> N774 + N856 --> N850 + N856 --> N777 + N856 --> N780 + N857 --> N426 + N857 --> N858 + N859 --> N860 + N859 --> N861 + N859 --> N862 + N859 --> N863 + N859 --> N864 + N859 --> N865 + N859 --> N866 + N859 --> N867 + N859 --> N868 + N859 --> N869 + N859 --> N870 + N859 --> N871 + N859 --> N872 + N859 --> N873 + N859 --> N874 + N859 --> N875 + N859 --> N876 + N859 --> N877 + N859 --> N878 + N859 --> N879 + N859 --> N880 + N859 --> N881 + N859 --> N882 + N859 --> N883 + N859 --> N884 + N859 --> N885 + N859 --> N886 + N859 --> N887 + N859 --> N888 + N859 --> N889 + N859 --> N890 + N859 --> N891 + N859 --> N892 + N859 --> N893 + N859 --> N894 + N895 --> N774 + N895 --> N850 + N895 --> N853 + N895 --> N779 + N895 --> N844 + N895 --> N857 + N896 --> N897 + N896 --> N853 + N896 --> N898 + N896 --> N899 + N899 --> N54 + N898 --> N54 + N855 --> N54 + N855 --> N22 + N900 --> N54 + N900 --> N774 + N901 --> N426 + N901 --> N452 + N901 --> N845 + N901 --> N818 + N901 --> N863 + N901 --> N864 + N901 --> N871 + N901 --> N878 + N901 --> N879 + N901 --> N883 + N901 --> N774 + N901 --> N846 + N902 --> N452 + N902 --> N774 + N902 --> N850 + N902 --> N853 + N902 --> N779 + N902 --> N903 + N902 --> N857 + N902 --> N901 + N904 --> N774 + N905 --> N774 + N905 --> N777 + N905 --> N782 + N905 --> N797 + N905 --> N813 + N905 --> N906 + N905 --> N907 + N908 --> N873 + N908 --> N909 + N908 --> N774 + N908 --> N777 + N908 --> N782 + N908 --> N797 + N908 --> N812 + N908 --> N813 + N910 --> N179 + N910 --> N774 + N911 --> N774 + N911 --> N777 + N911 --> N797 + N911 --> N813 + N912 --> N913 + N912 --> N887 + N912 --> N890 + N912 --> N893 + N912 --> N774 + N912 --> N850 + N912 --> N777 + N906 --> N774 + N906 --> N775 + N906 --> N797 + N906 --> N914 + N915 --> N863 + N915 --> N916 + N915 --> N876 + N915 --> N879 + N915 --> N892 + N915 --> N774 + N915 --> N777 + N907 --> N797 + N917 --> N774 + N917 --> N777 + N917 --> N797 + N917 --> N813 + N917 --> N906 + N917 --> N907 + N918 --> N797 + N919 --> N796 + N919 --> N920 + N920 --> N183 + N920 --> N179 + N921 --> N853 N921 --> N922 - N922 --> N746 - N922 --> N869 - N825 --> N746 - N825 --> N752 - N825 --> N751 - N923 --> N758 - N923 --> N924 - N923 --> N750 - N923 --> N746 - N923 --> N751 - N925 --> N35 - N925 --> N33 - N926 --> N17 - N869 --> N825 - N869 --> N751 - N927 --> N17 - N927 --> N817 - N927 --> N746 - N927 --> N818 - N927 --> N749 - N928 --> N768 - N928 --> N929 - N930 --> N35 - N930 --> N746 - N930 --> N749 - N931 --> N746 - N875 --> N746 - N932 --> N770 - N933 --> N768 - N933 --> N770 - N934 --> N862 - N934 --> N746 - N935 --> N897 - N935 --> N33 - N935 --> N832 - N935 --> N837 - N935 --> N847 - N935 --> N849 - N935 --> N854 - N935 --> N746 - N798 --> N17 - N797 --> N139 - N797 --> N33 - N797 --> N925 - N936 --> N746 - N936 --> N768 - N937 --> N17 - N894 --> N825 - N894 --> N751 - N938 --> N35 - N938 --> N746 - N938 --> N751 - N939 --> N746 - N939 --> N797 - N939 --> N798 - N939 --> N751 - N939 --> N937 - N940 --> N941 - N942 --> N17 - N942 --> N817 - N942 --> N746 - N942 --> N818 + N921 --> N898 + N921 --> N899 + N923 --> N54 + N923 --> N774 + N923 --> N850 + N923 --> N853 + N924 --> N909 + N924 --> N774 + N924 --> N850 + N924 --> N777 + N852 --> N285 + N852 --> N183 + N852 --> N179 + N852 --> N733 + N852 --> N414 + N852 --> N925 + N852 --> N22 + N852 --> N734 + N852 --> N774 + N852 --> N926 + N852 --> N777 + N852 --> N813 + N852 --> N927 + N852 --> N831 + N852 --> N928 + N928 --> N285 + N928 --> N183 + N928 --> N179 + N928 --> N733 + N928 --> N925 + N928 --> N734 + N928 --> N774 + N928 --> N926 + N928 --> N775 + N928 --> N777 + N929 --> N789 + N930 --> N813 + N931 --> N777 + N854 --> N54 + N932 --> N183 + N932 --> N179 + N933 --> N54 + N934 --> N796 + N935 --> N786 + N936 --> N786 + N937 --> N774 + N937 --> N779 + N938 --> N774 + N939 --> N54 + N939 --> N774 + N939 --> N938 + N784 --> N774 + N784 --> N808 + N779 --> N940 + N779 --> N941 + N942 --> N774 + N850 --> N774 + N850 --> N808 + N850 --> N777 + N850 --> N858 + N850 --> N779 + N943 --> N796 + N943 --> N810 + N944 --> N774 + N944 --> N779 + N945 --> N796 + N946 --> N846 + N947 --> N452 + N947 --> N774 + N947 --> N775 + N948 --> N796 + N948 --> N858 + N949 --> N818 + N949 --> N774 + N949 --> N850 + N949 --> N950 + N950 --> N774 + N950 --> N897 + N853 --> N774 + N853 --> N780 + N853 --> N779 + N951 --> N786 + N951 --> N952 + N951 --> N778 + N951 --> N774 + N951 --> N779 + N953 --> N54 + N953 --> N22 + N954 --> N17 + N897 --> N853 + N897 --> N779 + N955 --> N17 + N955 --> N845 + N955 --> N774 + N955 --> N846 + N955 --> N777 + N956 --> N796 + N956 --> N957 + N958 --> N54 + N958 --> N774 + N958 --> N777 + N959 --> N774 + N903 --> N774 + N960 --> N798 + N961 --> N796 + N961 --> N798 + N962 --> N890 + N962 --> N774 + N963 --> N925 + N963 --> N22 + N963 --> N860 + N963 --> N865 + N963 --> N875 + N963 --> N877 + N963 --> N882 + N963 --> N774 + N826 --> N17 + N825 --> N196 + N825 --> N22 + N825 --> N953 + N964 --> N774 + N964 --> N796 + N965 --> N17 + N922 --> N853 + N922 --> N779 + N966 --> N54 + N966 --> N774 + N966 --> N779 + N967 --> N774 + N967 --> N825 + N967 --> N826 + N967 --> N779 + N967 --> N965 + N968 --> N969 + N970 --> N17 + N970 --> N845 + N970 --> N774 + N970 --> N846 + N971 --> N81 + N972 --> N846 ``` diff --git a/docs/architecture/indexion-dependency-graph.md b/docs/architecture/indexion-dependency-graph.md index 6161b14c6..d5d00e66c 100644 --- a/docs/architecture/indexion-dependency-graph.md +++ b/docs/architecture/indexion-dependency-graph.md @@ -1,13 +1,15 @@ # solid-imager source dependencies (indexion) -The complete indexion graph contains 1,736 edges, while the default Mermaid renderer allows 500 edges. Use the split diagrams below for VS Code preview. +The complete indexion graph contains 1,810 edges, while the default Mermaid renderer allows 500 edges. Use the split diagrams below for VS Code preview. | Part | Scope | Edges | | --- | --- | ---: | -| [01 apps and core](indexion-dependency-graph-01-apps-and-core.md) | CLI, Tauri, xtracter, and shared core packages | 411 | -| [02 server tests](indexion-dependency-graph-02-server-tests.md) | `apps/server/src/tests` | 329 | -| [03 server infrastructure and routes](indexion-dependency-graph-03-server-infrastructure-routes.md) | `apps/server/src/infrastructure` and `routes` | 464 | -| [04 server components and root modules](indexion-dependency-graph-04-server-application-components.md) | server components, domain, and root modules | 119 | -| [05 UI](indexion-dependency-graph-05-ui.md) | `packages/ui/src` | 413 | +| [01 apps and core](indexion-dependency-graph-01-apps-and-core.md) | CLI, Tauri, xtracter, and shared core packages | 431 | +| [02 server tests](indexion-dependency-graph-02-server-tests.md) | `apps/server/src/tests` | 333 | +| [03 server infrastructure and routes](indexion-dependency-graph-03-server-infrastructure-routes.md) | `apps/server/src/infrastructure` and `routes` | 514 | +| [04 server components and root modules](indexion-dependency-graph-04-server-application-components.md) | server components, config, root modules, and route dependencies | 117 | +| [05 UI](indexion-dependency-graph-05-ui.md) | `packages/ui/src` | 415 | The un-split graph is preserved in [indexion-dependency-graph-full.md](indexion-dependency-graph-full.md) for tools that allow a higher `maxEdges` value. + +The browser-history behavior and snapshot contract are described in [search-history.md](search-history.md). diff --git a/docs/architecture/package-dependency-graph.md b/docs/architecture/package-dependency-graph.md index da946da46..07a567483 100644 --- a/docs/architecture/package-dependency-graph.md +++ b/docs/architecture/package-dependency-graph.md @@ -1,5 +1,7 @@ # solid-imager package dependencies +The edge labels are cross-package import counts from the scoped indexion graph; package-local and external dependency edges are omitted. + ```mermaid flowchart LR %% Aggregated from indexion's source dependency graph. @@ -24,22 +26,22 @@ flowchart LR cli -->|1 import| client cli -->|1 import| core - server -->|24 imports| application - server -->|4 imports| client - server -->|138 imports| core - server -->|16 imports| db - server -->|89 imports| ui + server -->|26 imports| application + server -->|3 imports| client + server -->|146 imports| core + server -->|17 imports| db + server -->|108 imports| ui - tauri -->|2 imports| client - tauri -->|11 imports| core - tauri -->|45 imports| ui + tauri -->|3 imports| client + tauri -->|12 imports| core + tauri -->|50 imports| ui xtracter -->|1 import| client xtracter -->|1 import| core - application -->|24 imports| core - db -->|6 imports| core - ui -->|99 imports| core + application -->|23 imports| core + db -->|7 imports| core + ui -->|100 imports| core classDef app fill:#e8f1ff,stroke:#4f76a8,color:#132238; classDef pkg fill:#eef8ee,stroke:#5b8a5a,color:#173117; diff --git a/docs/architecture/search-history.md b/docs/architecture/search-history.md new file mode 100644 index 000000000..a8fa4181c --- /dev/null +++ b/docs/architecture/search-history.md @@ -0,0 +1,66 @@ +# Search history and browser navigation + +検索結果は検索ストアの現在値だけでなく、ブラウザの履歴エントリごとに復元できる状態として扱う。これにより、検索 → 詳細画面 → 戻る/進むという遷移でも、各検索結果を同じ画面に再表示できる。 + +## 保存する状態 + +`SearchSnapshotState` は `packages/core/src/domain/search/schema.ts` の `searchStateSchema` から、結果を決めない UI ローカル値を除いたスナップショットである。 + +- 除外: `activePresetId`, `offset`, `scrollY` +- 含む: mode、検索語、タグ/エンティティの絞り込み、類似検索の anchor と topK、limit、sort +- 履歴 state: `history.state.searchSnapshot = { version: 1, id?, state }` +- URL: サーバーに保存されたスナップショットは `?search=` で参照する + +スクロール位置は検索条件とは別に、ルーターが提供する履歴エントリキーを含めて保存する。したがって、同じ検索条件を別の履歴エントリで開いた場合もスクロール位置が混ざらない。 + +## ライフサイクル + +```mermaid +sequenceDiagram + participant Store as Search store + participant Hook as useSearchHistoryPersistence + participant History as Browser history + participant API as searchSnapshots API + + Store->>Hook: result-changing state settles + Hook->>History: push({ searchSnapshot: { version, state } }) + Hook->>API: capture(state) + API-->>Hook: snapshot id + Hook->>History: replace URL with ?search=id + + History->>Hook: back/forward or direct URL + alt history.state contains the snapshot + Hook->>Hook: validate and restore local state + else URL contains search id + Hook->>API: get(id) + API-->>Hook: snapshot state + Hook->>Hook: reset store, apply state, offset = 0 + else no snapshot exists + Hook->>Hook: restore current session state and create a local entry + end +``` + +`useSearchHistoryPersistence` は通常の入力変更を 500 ms デバウンスして履歴へ追加し、明示的な検索操作は `commitNow` で即時確定する。復元処理で同じ state がストアから再通知されても、新しい履歴エントリを作らない。 + +## サーバー側スナップショット + +`packages/core/src/domain/contract/search-snapshots.contract.ts` の contract と `apps/server/src/infrastructure/api/routers/search-snapshots-router.ts` の router が次の API を提供する。 + +| Operation | Input | Output | 用途 | +| --- | --- | --- | --- | +| `searchSnapshots.capture` | `SearchSnapshotState` | `{ id }` | state を fingerprint 付きで保存し、同じ検索を再利用する | +| `searchSnapshots.get` | UUID | `SearchSnapshot` | 直接 URL、リロード、履歴復元から state を取得する | + +保存先は `packages/db/src/schema.ts` の `search_snapshots` テーブルで、`fingerprint` は一意、`state` は JSON、`created_at` には index を持つ。fingerprint は version と state をキー順に安定シリアライズして SHA-256 化するため、保存順に依存しない。 + +## 適用範囲とフォールバック + +- Web と Tauri の global search、source search、legacy、v2 route は `searchHistoryQuerySchema` で `search` query を検証する。 +- 共通の復元処理は `packages/ui/src/hooks/use-search-history-persistence.ts` に置き、route 側は API client と surface を渡すだけにする。 +- API が利用できない場合は `history.state` のローカルエントリをそのまま利用する。 +- UUID が削除済み・不正な場合は query を除去し、現在の session state に戻す。履歴操作自体は失敗させない。 +- user preset には自動保存した検索を混ぜない。preset は検索 state の復元元として別に扱う。 + +## 検証 + +`apps/server/src/tests/e2e/search-history.spec.ts` は、通常検索と類似検索を別々の履歴エントリにした後、詳細画面から `goBack()` を複数回実行し、各検索結果と direct URL reload が復元されることを確認する。 diff --git a/packages/application/src/index.ts b/packages/application/src/index.ts index c0f5fc71e..d00df1381 100644 --- a/packages/application/src/index.ts +++ b/packages/application/src/index.ts @@ -13,6 +13,7 @@ export type { IPresetService, IProjectService, ISearchService, + ISearchSnapshotService, ISourceEventPublisher, ITaggingService, ITagService, @@ -29,6 +30,7 @@ export { createIpService, createPresetService, createProjectService, + createSearchSnapshotService, createTagService, createUserService, MediaProcessingServiceImpl, diff --git a/packages/application/src/ports/index.ts b/packages/application/src/ports/index.ts index a14fbe3e6..25913d1f3 100644 --- a/packages/application/src/ports/index.ts +++ b/packages/application/src/ports/index.ts @@ -24,6 +24,7 @@ export type { export type { IPresetService } from "./preset-service"; export type { IProjectService } from "./project-service"; export type { ISearchService, SearchOptions } from "./search-service"; +export type { ISearchSnapshotService } from "./search-snapshot-service"; export type { ITagService } from "./tag-service"; export type { ITaggingService } from "./tagging-service"; export type { IThumbnailService } from "./thumbnail-service"; diff --git a/packages/application/src/ports/search-snapshot-service.ts b/packages/application/src/ports/search-snapshot-service.ts new file mode 100644 index 000000000..d4e93c6d3 --- /dev/null +++ b/packages/application/src/ports/search-snapshot-service.ts @@ -0,0 +1,9 @@ +import type { + SearchSnapshot, + SearchSnapshotState, +} from "@solid-imager/core/domain/search/history"; + +export interface ISearchSnapshotService { + capture(state: SearchSnapshotState): Promise; + get(id: string): Promise; +} diff --git a/packages/application/src/services/index.ts b/packages/application/src/services/index.ts index 976d13604..0d67608e1 100644 --- a/packages/application/src/services/index.ts +++ b/packages/application/src/services/index.ts @@ -17,6 +17,7 @@ export { MediaUploadService } from "./media-upload-service"; export { createPresetService } from "./preset-service"; export { createProjectService } from "./project-service"; export { SearchServiceImpl } from "./search-service"; +export { createSearchSnapshotService } from "./search-snapshot-service"; export { createTagService } from "./tag-service"; export { TaggingServiceImpl } from "./tagging-service"; export { ThumbnailServiceImpl } from "./thumbnail-service"; diff --git a/packages/application/src/services/search-snapshot-service.ts b/packages/application/src/services/search-snapshot-service.ts new file mode 100644 index 000000000..37a933bd7 --- /dev/null +++ b/packages/application/src/services/search-snapshot-service.ts @@ -0,0 +1,55 @@ +import { createHash } from "node:crypto"; +import { ResourceNotFoundError } from "@solid-imager/core/domain/errors"; +import type { SearchSnapshotRepository } from "@solid-imager/core/domain/repositories/search-snapshot-repository"; +import { + type SearchSnapshot, + type SearchSnapshotState, + searchSnapshotStateSchema, +} from "@solid-imager/core/domain/search/history"; +import type { ISearchSnapshotService } from "../ports/search-snapshot-service"; + +function stableSerialize(value: unknown): string { + if (Array.isArray(value)) { + return `[${value.map(stableSerialize).join(",")}]`; + } + if (value !== null && typeof value === "object") { + const entries = Object.entries(value).sort(([left], [right]) => + left < right ? -1 : left > right ? 1 : 0, + ); + return `{${entries + .map( + ([key, entryValue]) => + `${JSON.stringify(key)}:${stableSerialize(entryValue)}`, + ) + .join(",")}}`; + } + return JSON.stringify(value) ?? "null"; +} + +function fingerprintState(state: SearchSnapshotState): string { + return createHash("sha256") + .update(stableSerialize({ version: 1, state })) + .digest("hex"); +} + +export function createSearchSnapshotService( + repository: SearchSnapshotRepository, +): ISearchSnapshotService { + return { + async capture(input): Promise { + const state = searchSnapshotStateSchema.parse(input); + const fingerprint = fingerprintState(state); + return repository.create({ fingerprint, state }); + }, + + async get(id): Promise { + const snapshot = await repository.get(id); + if (!snapshot) { + throw new ResourceNotFoundError("SearchSnapshot", id); + } + return snapshot; + }, + }; +} + +export { fingerprintState, stableSerialize }; diff --git a/packages/core/src/domain/contract/index.ts b/packages/core/src/domain/contract/index.ts index 9885d46fc..d16c76144 100644 --- a/packages/core/src/domain/contract/index.ts +++ b/packages/core/src/domain/contract/index.ts @@ -12,6 +12,8 @@ export { mediaContract } from "./media.contract"; export { presetsContract } from "./presets.contract"; export type { PresetOrpcLike } from "./presets-client"; export { projectsContract } from "./projects.contract"; +export { searchSnapshotsContract } from "./search-snapshots.contract"; +export type { SearchSnapshotsOrpcLike } from "./search-snapshots-client"; export { sourcesContract } from "./sources.contract"; export { tagsContract } from "./tags.contract"; export { thumbnailsContract } from "./thumbnails.contract"; @@ -30,6 +32,7 @@ import { jobsContract } from "./jobs.contract"; import { mediaContract } from "./media.contract"; import { presetsContract } from "./presets.contract"; import { projectsContract } from "./projects.contract"; +import { searchSnapshotsContract } from "./search-snapshots.contract"; import { sourcesContract } from "./sources.contract"; import { tagsContract } from "./tags.contract"; import { thumbnailsContract } from "./thumbnails.contract"; @@ -57,6 +60,7 @@ export const appContract = { utils: utilsContract, config: configContract, presets: presetsContract, + searchSnapshots: searchSnapshotsContract, }; export type AppContract = typeof appContract; diff --git a/packages/core/src/domain/contract/search-snapshots-client.ts b/packages/core/src/domain/contract/search-snapshots-client.ts new file mode 100644 index 000000000..9c4bfd988 --- /dev/null +++ b/packages/core/src/domain/contract/search-snapshots-client.ts @@ -0,0 +1,6 @@ +import type { ContractRouterClient } from "@orpc/contract"; +import type { searchSnapshotsContract } from "./search-snapshots.contract"; + +export type SearchSnapshotsOrpcLike = { + searchSnapshots: ContractRouterClient; +}; diff --git a/packages/core/src/domain/contract/search-snapshots.contract.ts b/packages/core/src/domain/contract/search-snapshots.contract.ts new file mode 100644 index 000000000..0c0abdbb6 --- /dev/null +++ b/packages/core/src/domain/contract/search-snapshots.contract.ts @@ -0,0 +1,14 @@ +import { oc } from "@orpc/contract"; +import { + captureSearchSnapshotRequestSchema, + captureSearchSnapshotResponseSchema, + getSearchSnapshotRequestSchema, + searchSnapshotSchema, +} from "../search/history"; + +export const searchSnapshotsContract = { + capture: oc + .input(captureSearchSnapshotRequestSchema) + .output(captureSearchSnapshotResponseSchema), + get: oc.input(getSearchSnapshotRequestSchema).output(searchSnapshotSchema), +}; diff --git a/packages/core/src/domain/repositories/search-snapshot-repository.ts b/packages/core/src/domain/repositories/search-snapshot-repository.ts new file mode 100644 index 000000000..4a7114fe7 --- /dev/null +++ b/packages/core/src/domain/repositories/search-snapshot-repository.ts @@ -0,0 +1,13 @@ +import type { + SearchSnapshot, + SearchSnapshotState, +} from "@/domain/search/history"; + +export type SearchSnapshotRepository = { + get(id: string): Promise; + getByFingerprint(fingerprint: string): Promise; + create(data: { + fingerprint: string; + state: SearchSnapshotState; + }): Promise; +}; diff --git a/packages/core/src/domain/search/history.ts b/packages/core/src/domain/search/history.ts new file mode 100644 index 000000000..20640ef52 --- /dev/null +++ b/packages/core/src/domain/search/history.ts @@ -0,0 +1,54 @@ +import { z } from "zod"; +import { searchStateSchema } from "./schema"; + +/** + * The part of the search state that determines a result query. + * + * Pagination, scroll position, and the selected user preset are local UI + * concerns. Keeping them out of the snapshot makes the same search reusable + * across different list surfaces and prevents scrolling from creating a new + * search history item. + */ +export const searchSnapshotStateSchema = searchStateSchema.omit({ + activePresetId: true, + offset: true, + scrollY: true, +}); + +export type SearchSnapshotState = z.infer; + +export const searchSnapshotVersionSchema = z.literal(1); + +export const searchSnapshotSchema = z.object({ + id: z.uuid({ version: "v4" }), + version: searchSnapshotVersionSchema, + fingerprint: z.string().min(1), + state: searchSnapshotStateSchema, + createdAt: z.coerce.date(), +}); + +export type SearchSnapshot = z.infer; + +export const captureSearchSnapshotRequestSchema = z.object({ + state: searchSnapshotStateSchema, +}); + +export type CaptureSearchSnapshotRequest = z.infer< + typeof captureSearchSnapshotRequestSchema +>; + +export const captureSearchSnapshotResponseSchema = z.object({ + id: z.uuid({ version: "v4" }), +}); + +export type CaptureSearchSnapshotResponse = z.infer< + typeof captureSearchSnapshotResponseSchema +>; + +export const getSearchSnapshotRequestSchema = z.object({ + id: z.uuid({ version: "v4" }), +}); + +export type GetSearchSnapshotRequest = z.infer< + typeof getSearchSnapshotRequestSchema +>; diff --git a/packages/core/src/domain/sources/store.ts b/packages/core/src/domain/sources/store.ts index 7e4cd3f08..73b67dfdb 100644 --- a/packages/core/src/domain/sources/store.ts +++ b/packages/core/src/domain/sources/store.ts @@ -38,13 +38,33 @@ const persistToStorage = () => { sessionStorage.setItem(STORAGE_KEY, data); }; -export const getScrollPosition = (mediaSourceId: string) => { - const position = sourcesState.scrollPositions[mediaSourceId] || 0; +function getScrollKey(mediaSourceId: string, historyEntryKey?: string): string { + return historyEntryKey + ? `${mediaSourceId}::history:${historyEntryKey}` + : mediaSourceId; +} + +export const getScrollPosition = ( + mediaSourceId: string, + historyEntryKey?: string, +) => { + const position = + sourcesState.scrollPositions[ + getScrollKey(mediaSourceId, historyEntryKey) + ] || 0; return position; }; -export const setScrollPosition = (mediaSourceId: string, scrollY: number) => { - setSourcesState("scrollPositions", mediaSourceId, scrollY); +export const setScrollPosition = ( + mediaSourceId: string, + scrollY: number, + historyEntryKey?: string, +) => { + setSourcesState( + "scrollPositions", + getScrollKey(mediaSourceId, historyEntryKey), + scrollY, + ); // Persist immediately after updating persistToStorage(); }; diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 15bac8a88..4062cc73c 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -10,6 +10,7 @@ export { createMediaRepository } from "./repositories/media-repository"; export { createMediaSearchFunctions } from "./repositories/media-repository-utils"; export { createPresetRepository } from "./repositories/preset-repository"; export { createProjectRepository } from "./repositories/project-repository"; +export { createSearchSnapshotRepository } from "./repositories/search-snapshot-repository"; export { createSourceRepository } from "./repositories/source-repository"; export { createTagRepository } from "./repositories/tag-repository"; export { createUserRepository } from "./repositories/user-repository"; diff --git a/packages/db/src/repositories/search-snapshot-repository.ts b/packages/db/src/repositories/search-snapshot-repository.ts new file mode 100644 index 000000000..a86fa8309 --- /dev/null +++ b/packages/db/src/repositories/search-snapshot-repository.ts @@ -0,0 +1,64 @@ +import type { SearchSnapshotRepository } from "@solid-imager/core/domain/repositories/search-snapshot-repository"; +import { + type SearchSnapshot, + type SearchSnapshotState, + searchSnapshotSchema, + searchSnapshotStateSchema, +} from "@solid-imager/core/domain/search/history"; +import { eq } from "drizzle-orm"; +import { type SearchSnapshotRow, searchSnapshots } from "../schema"; +import type { DrizzleExecutor } from "../types"; + +function mapToEntity(row: SearchSnapshotRow): SearchSnapshot { + return searchSnapshotSchema.parse({ + id: row.id, + version: row.version, + fingerprint: row.fingerprint, + state: searchSnapshotStateSchema.parse(row.state), + createdAt: row.createdAt, + }); +} + +export function createSearchSnapshotRepository( + getExecutor: (tx?: unknown) => DrizzleExecutor, +): SearchSnapshotRepository { + const getBy = async ( + column: typeof searchSnapshots.id | typeof searchSnapshots.fingerprint, + value: string, + ): Promise => { + const rows = await getExecutor() + .select() + .from(searchSnapshots) + .where(eq(column, value)) + .limit(1); + return rows[0] ? mapToEntity(rows[0]) : null; + }; + + return { + get: (id) => getBy(searchSnapshots.id, id), + getByFingerprint: (fingerprint) => + getBy(searchSnapshots.fingerprint, fingerprint), + async create(data: { + fingerprint: string; + state: SearchSnapshotState; + }): Promise { + await getExecutor() + .insert(searchSnapshots) + .values({ + fingerprint: data.fingerprint, + state: data.state, + version: 1, + }) + .onConflictDoNothing({ target: searchSnapshots.fingerprint }); + + const snapshot = await getBy( + searchSnapshots.fingerprint, + data.fingerprint, + ); + if (!snapshot) { + throw new Error("Failed to create search snapshot"); + } + return snapshot; + }, + }; +} diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index bb2750fe0..7369dfc87 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -1119,6 +1119,26 @@ export const presets = pgTable("presets", { createdAt: timestamp("created_at").notNull().defaultNow(), }); +/** + * Immutable server-side search states referenced by browser history URLs. + * User-facing presets remain separate from automatically captured searches. + */ +export const searchSnapshots = pgTable( + "search_snapshots", + { + id: uuid("id").primaryKey().default(sql`gen_random_uuid()`), + version: integer("version").notNull().default(1), + fingerprint: text("fingerprint").notNull().unique(), + state: jsonb("state").notNull(), + createdAt: timestamp("created_at").notNull().defaultNow(), + }, + (table) => ({ + createdAtIndex: index("idx_search_snapshots_created_at").on( + table.createdAt, + ), + }), +); + // リレーション /** * Defines the relations for the media_sources table. @@ -1736,6 +1756,10 @@ export type Preset = InferSelectModel; */ export type NewPreset = InferInsertModel; +export type SearchSnapshotRow = InferSelectModel; + +export type NewSearchSnapshotRow = InferInsertModel; + /** * Type definition for selecting a media relation from the database. */ diff --git a/packages/ui/src/hooks/use-current-search-persistence.ts b/packages/ui/src/hooks/use-current-search-persistence.ts index c665c6486..794ed9c09 100644 --- a/packages/ui/src/hooks/use-current-search-persistence.ts +++ b/packages/ui/src/hooks/use-current-search-persistence.ts @@ -25,6 +25,7 @@ export type { SearchPersistenceSurface } from "../stores/search-store"; export type SearchPersistenceOptions = { surface?: SearchPersistenceSurface; + historyEntryKey?: string | Accessor; }; export type SearchPersistenceSource = @@ -44,8 +45,11 @@ function getCurrentPresetName(sourceId: string | null | undefined) { function getScrollStorageKey( presetName: string, surface: SearchPersistenceSurface, + historyEntryKey?: string, ): string { - return `search-scroll:${surface}:${presetName}`; + return historyEntryKey + ? `search-scroll:${surface}:history:${historyEntryKey}` + : `search-scroll:${surface}:${presetName}`; } function getStateStorageKey( @@ -97,8 +101,16 @@ export function persistSearchScrollPosition( } try { + const historyEntryKey = + typeof options.historyEntryKey === "function" + ? options.historyEntryKey() + : options.historyEntryKey; sessionStorage.setItem( - getScrollStorageKey(presetName, options.surface ?? "legacy"), + getScrollStorageKey( + presetName, + options.surface ?? "legacy", + historyEntryKey, + ), String(normalizeScrollPosition(position)), ); } catch { @@ -106,6 +118,32 @@ export function persistSearchScrollPosition( } } +export function readPersistedSearchScrollPosition( + sourceId: SearchPersistenceSource = "current", + options: SearchPersistenceOptions = {}, +): number { + if (isServer) return 0; + const resolvedSourceId = + typeof sourceId === "function" ? sourceId() : sourceId; + const presetName = getCurrentPresetName(resolvedSourceId); + if (!presetName) return 0; + const historyEntryKey = + typeof options.historyEntryKey === "function" + ? options.historyEntryKey() + : options.historyEntryKey; + try { + return readScrollPosition( + getScrollStorageKey( + presetName, + options.surface ?? "legacy", + historyEntryKey, + ), + ); + } catch { + return 0; + } +} + function resetSearchStatePreservingScroll() { const preservedScrollY = searchState.scrollY; resetSearchState(); diff --git a/packages/ui/src/hooks/use-search-history-persistence.ts b/packages/ui/src/hooks/use-search-history-persistence.ts new file mode 100644 index 000000000..1a82d6986 --- /dev/null +++ b/packages/ui/src/hooks/use-search-history-persistence.ts @@ -0,0 +1,296 @@ +import type { + SearchSnapshot, + SearchSnapshotState, +} from "@solid-imager/core/domain/search/history"; +import { searchSnapshotStateSchema } from "@solid-imager/core/domain/search/history"; +import { useLocation, useRouter } from "@tanstack/solid-router"; +import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"; +import { isServer } from "solid-js/web"; +import type { SearchHistoryClient } from "../search-history-client"; +import { + resetSearchState, + searchState, + setSearchState, +} from "../stores/search-store"; +import { + readPersistedSearchScrollPosition, + type SearchPersistenceOptions, + type SearchPersistenceSource, + useCurrentSearchPersistence, +} from "./use-current-search-persistence"; + +const HISTORY_VERSION = 1 as const; +const HISTORY_COMMIT_DEBOUNCE_MS = 500; + +type HistorySnapshotEntry = { + version: typeof HISTORY_VERSION; + id?: string; + state: SearchSnapshotState; +}; + +type SearchHistoryOptions = SearchPersistenceOptions & { + client: SearchHistoryClient; +}; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function getRecord(value: unknown): Record { + return isRecord(value) ? value : {}; +} + +function parseHistoryEntry(value: unknown): HistorySnapshotEntry | null { + const record = getRecord(value); + const candidate = record.searchSnapshot; + if (!isRecord(candidate) || candidate.version !== HISTORY_VERSION) { + return null; + } + const parsed = searchSnapshotStateSchema.safeParse(candidate.state); + if (!parsed.success) { + return null; + } + return { + version: HISTORY_VERSION, + ...(typeof candidate.id === "string" ? { id: candidate.id } : {}), + state: parsed.data, + }; +} + +function readSnapshotState(): SearchSnapshotState { + return searchSnapshotStateSchema.parse({ + mode: searchState.mode, + searchQuery: searchState.searchQuery, + selectedTags: [...searchState.selectedTags], + excludeTags: [...searchState.excludeTags], + tagMode: searchState.tagMode, + selectedSource: searchState.selectedSource, + selectedProjects: [...searchState.selectedProjects], + selectedIps: [...searchState.selectedIps], + selectedCharacters: [...searchState.selectedCharacters], + selectedAuthors: [...searchState.selectedAuthors], + advancedCondition: searchState.advancedCondition, + similarityAnchorMediaId: searchState.similarityAnchorMediaId, + similarityTopK: searchState.similarityTopK, + limit: searchState.limit, + sortBy: searchState.sortBy, + sortOrder: searchState.sortOrder, + }); +} + +function stateKey(state: SearchSnapshotState): string { + return JSON.stringify(state); +} + +function applySnapshotState(state: SearchSnapshotState, scrollY = 0): void { + resetSearchState(); + setSearchState({ ...state, offset: 0, scrollY }); +} + +function createPathWithoutSnapshot(href: string): string { + const url = new URL(href, "http://solid-imager.invalid"); + url.searchParams.delete("search"); + return `${url.pathname}${url.search}${url.hash}`; +} + +function createPathWithSnapshot(href: string, id: string): string { + const url = new URL(href, "http://solid-imager.invalid"); + url.searchParams.set("search", id); + return `${url.pathname}${url.search}${url.hash}`; +} + +function readSnapshotId(href: string): string | undefined { + const id = new URL(href, "http://solid-imager.invalid").searchParams.get( + "search", + ); + return id || undefined; +} + +function readHistoryEntryKey(state: unknown, href: string): string { + const record = getRecord(state); + const key = record.__TSR_key ?? record.key; + return typeof key === "string" && key.length > 0 ? key : href; +} + +function mergeHistoryEntry( + state: unknown, + entry: HistorySnapshotEntry, +): Record { + return { + ...getRecord(state), + searchSnapshot: entry, + }; +} + +/** + * Restores a search state from the URL/history entry and captures settled + * result-changing state as browser history entries. + */ +export function useSearchHistoryPersistence( + sourceId: SearchPersistenceSource, + options: SearchHistoryOptions, +) { + const sessionRestored = useCurrentSearchPersistence(sourceId, options); + const router = useRouter(); + const location = useLocation(); + const [historyRestored, setHistoryRestored] = createSignal(false); + let lastEntryKey: string | null = null; + let lastSessionReady: boolean | null = null; + let skipNextCommitState: string | null = null; + let restoreGeneration = 0; + let commitTimer: ReturnType | null = null; + + const isRestored = createMemo(() => sessionRestored() && historyRestored()); + + const currentHref = () => router.history.location.href; + const currentHistoryState = () => router.history.location.state; + const currentEntryKey = () => + readHistoryEntryKey(currentHistoryState(), currentHref()); + + const replaceWithLocalEntry = (entry: HistorySnapshotEntry): string => { + router.history.replace( + createPathWithoutSnapshot(currentHref()), + mergeHistoryEntry(currentHistoryState(), entry), + ); + router.history.flush(); + return currentEntryKey(); + }; + + const captureForCurrentEntry = async ( + entryKey: string, + state: SearchSnapshotState, + ) => { + try { + const response = await options.client.capture(state); + if (currentEntryKey() !== entryKey) return; + const currentEntry = parseHistoryEntry(currentHistoryState()); + const nextEntry: HistorySnapshotEntry = { + version: HISTORY_VERSION, + id: response.id, + state: currentEntry?.state ?? state, + }; + const path = createPathWithSnapshot(currentHref(), response.id); + router.history.replace( + path, + mergeHistoryEntry(currentHistoryState(), nextEntry), + ); + router.history.flush(); + } catch { + // The local history entry remains usable when the server is unavailable. + } + }; + + createEffect(() => { + if (isServer) return; + location(); + const sessionReady = sessionRestored(); + const href = currentHref(); + const entryKey = currentEntryKey(); + if (entryKey === lastEntryKey && sessionReady === lastSessionReady) return; + lastEntryKey = entryKey; + lastSessionReady = sessionReady; + const generation = ++restoreGeneration; + setHistoryRestored(false); + const queryId = readSnapshotId(href); + const localEntry = parseHistoryEntry(currentHistoryState()); + + const restore = async () => { + let snapshotState: SearchSnapshotState | null = null; + if (queryId && localEntry?.id === queryId) { + snapshotState = localEntry.state; + } else if (queryId) { + try { + const snapshot: SearchSnapshot = await options.client.get(queryId); + if (generation !== restoreGeneration) return; + snapshotState = snapshot.state; + } catch { + if (generation !== restoreGeneration) return; + // A deleted/invalid shared snapshot falls back to the current session. + router.history.replace( + createPathWithoutSnapshot(href), + getRecord(currentHistoryState()), + ); + router.history.flush(); + } + } else if (localEntry) { + snapshotState = localEntry.state; + } + + if (generation !== restoreGeneration) return; + if (snapshotState) { + applySnapshotState( + snapshotState, + readPersistedSearchScrollPosition(sourceId, { + surface: options.surface, + historyEntryKey: entryKey, + }), + ); + skipNextCommitState = stateKey(snapshotState); + setHistoryRestored(true); + return; + } + if (!sessionReady) { + setHistoryRestored(false); + return; + } + + const state = readSnapshotState(); + skipNextCommitState = stateKey(state); + const entry: HistorySnapshotEntry = { + version: HISTORY_VERSION, + state, + }; + const localEntryKey = replaceWithLocalEntry(entry); + setHistoryRestored(true); + void captureForCurrentEntry(localEntryKey, state); + }; + + void restore(); + }); + + const commitCurrentState = () => { + if (!isRestored()) return; + const state = readSnapshotState(); + const key = stateKey(state); + if (skipNextCommitState === key) { + skipNextCommitState = null; + return; + } + const currentEntry = parseHistoryEntry(currentHistoryState()); + if (currentEntry && stateKey(currentEntry.state) === key) return; + skipNextCommitState = key; + const entry: HistorySnapshotEntry = { version: HISTORY_VERSION, state }; + router.history.push( + createPathWithoutSnapshot(currentHref()), + mergeHistoryEntry(currentHistoryState(), entry), + ); + router.history.flush(); + const pushedKey = currentEntryKey(); + void captureForCurrentEntry(pushedKey, state); + }; + + createEffect(() => { + if (isServer || !isRestored()) return; + const state = readSnapshotState(); + const key = stateKey(state); + if (skipNextCommitState === key) { + skipNextCommitState = null; + return; + } + if (commitTimer) clearTimeout(commitTimer); + commitTimer = setTimeout(() => { + commitTimer = null; + commitCurrentState(); + }, HISTORY_COMMIT_DEBOUNCE_MS); + }); + + onCleanup(() => { + if (commitTimer) clearTimeout(commitTimer); + }); + + return { + isRestored, + commitNow: commitCurrentState, + historyEntryKey: currentEntryKey, + }; +} diff --git a/packages/ui/src/hooks/use-search-page.ts b/packages/ui/src/hooks/use-search-page.ts index fbc0e8a6b..fba7d0ea0 100644 --- a/packages/ui/src/hooks/use-search-page.ts +++ b/packages/ui/src/hooks/use-search-page.ts @@ -82,6 +82,8 @@ export interface UseSearchPageOptions { gcTime?: number; refreshDebounceMs?: number; isSearchStateRestored?: Accessor; + commitSearchHistory?: () => void; + historyEntryKey?: Accessor; enableVirtualization?: boolean; scrollContainerSelector?: string; } @@ -135,6 +137,8 @@ export function useSearchPage( gcTime = DEFAULT_GC_TIME, refreshDebounceMs = DEFAULT_REFRESH_DEBOUNCE_MS, isSearchStateRestored = () => true, + commitSearchHistory, + historyEntryKey, enableVirtualization = false, } = options; const queryClient = useQueryClient(); @@ -340,7 +344,7 @@ export function useSearchPage( }; const isRestored = useScrollRestoration({ - restoreKey: () => "search", + restoreKey: () => historyEntryKey?.() ?? "search", getPosition: () => scrollY(), setPosition: (key, position) => { void key; @@ -367,6 +371,7 @@ export function useSearchPage( setOffset(0); setScrollY(0); scrollToPosition(options.scrollContainerSelector, 0); + commitSearchHistory?.(); }; const [loadMoreRef, setLoadMoreRef] = createSignal< diff --git a/packages/ui/src/hooks/use-source-media-page.ts b/packages/ui/src/hooks/use-source-media-page.ts index 3e5dc5fbf..ab2916e81 100644 --- a/packages/ui/src/hooks/use-source-media-page.ts +++ b/packages/ui/src/hooks/use-source-media-page.ts @@ -144,6 +144,8 @@ export type UseSourceMediaPageOptions = { itemsPerPage?: number; onThumbnailReady?: (mediaId: string) => void; isSearchStateRestored?: Accessor; + commitSearchHistory?: () => void; + historyEntryKey?: Accessor; enableVirtualization?: boolean; scrollContainerSelector?: string; }; @@ -215,6 +217,8 @@ export function useSourceMediaPage( itemsPerPage = SOURCE_MEDIA_ITEMS_PER_PAGE, onThumbnailReady, isSearchStateRestored = () => true, + commitSearchHistory, + historyEntryKey, enableVirtualization = false, } = options; @@ -291,8 +295,9 @@ export function useSourceMediaPage( // --- Search handler --- const handleSearch = () => { const sourceId = id(); - if (sourceId) setScrollPosition(sourceId, 0); + if (sourceId) setScrollPosition(sourceId, 0, historyEntryKey?.()); scrollToPosition(options.scrollContainerSelector, 0); + commitSearchHistory?.(); }; let nextPageFetchInFlight = false; @@ -314,9 +319,17 @@ export function useSourceMediaPage( // --- Scroll restoration --- useScrollRestoration({ - restoreKey: id, - getPosition: (sourceId) => getScrollPosition(sourceId), - setPosition: (sourceId, position) => setScrollPosition(sourceId, position), + restoreKey: () => historyEntryKey?.() ?? id(), + getPosition: () => { + const sourceId = id(); + return sourceId ? getScrollPosition(sourceId, historyEntryKey?.()) : 0; + }, + setPosition: (_key, position) => { + const sourceId = id(); + if (sourceId) { + setScrollPosition(sourceId, position, historyEntryKey?.()); + } + }, isReady: () => Boolean(mediaQueryData()) && !mediaQuery.isLoading, hasNextPage: () => mediaQuery.hasNextPage, isFetchingNextPage: () => mediaQuery.isFetchingNextPage, diff --git a/packages/ui/src/search-history-client.ts b/packages/ui/src/search-history-client.ts new file mode 100644 index 000000000..845924209 --- /dev/null +++ b/packages/ui/src/search-history-client.ts @@ -0,0 +1,19 @@ +import type { SearchSnapshotsOrpcLike } from "@solid-imager/core/domain/contract/search-snapshots-client"; +import type { + SearchSnapshot, + SearchSnapshotState, +} from "@solid-imager/core/domain/search/history"; + +export type SearchHistoryClient = { + capture: (state: SearchSnapshotState) => Promise<{ id: string }>; + get: (id: string) => Promise; +}; + +export function createSearchHistoryClient( + orpc: SearchSnapshotsOrpcLike, +): SearchHistoryClient { + return { + capture: async (state) => await orpc.searchSnapshots.capture({ state }), + get: async (id) => await orpc.searchSnapshots.get({ id }), + }; +} diff --git a/packages/ui/src/search-history-route.ts b/packages/ui/src/search-history-route.ts new file mode 100644 index 000000000..d3672d4d9 --- /dev/null +++ b/packages/ui/src/search-history-route.ts @@ -0,0 +1,5 @@ +import { z } from "zod"; + +export const searchHistoryQuerySchema = z.object({ + search: z.uuid({ version: "v4" }).optional(), +}); diff --git a/packages/ui/src/source-media-page.tsx b/packages/ui/src/source-media-page.tsx index d0432e143..26b9c265d 100644 --- a/packages/ui/src/source-media-page.tsx +++ b/packages/ui/src/source-media-page.tsx @@ -9,11 +9,9 @@ import type { TagResponse } from "@solid-imager/core/domain/tags/schemas"; import { createQuery, useQueryClient } from "@tanstack/solid-query"; import type { Accessor, Component, JSX } from "solid-js"; import { isServer } from "solid-js/web"; -import { - type SearchPersistenceSurface, - useCurrentSearchPersistence, -} from "./hooks/use-current-search-persistence"; +import type { SearchPersistenceSurface } from "./hooks/use-current-search-persistence"; import type { MediaSourceEventTransport } from "./hooks/use-media-source-events"; +import { useSearchHistoryPersistence } from "./hooks/use-search-history-persistence"; import { type SourceMediaPageActions, type SourceMediaPagePresetClient, @@ -22,6 +20,7 @@ import { import { MediaListActions } from "./media-list-actions"; import { toQueryUiState } from "./query-state"; import type { SourceMediaScreenProps } from "./screens/source-media-screen.types"; +import type { SearchHistoryClient } from "./search-history-client"; // biome-ignore lint/suspicious/noExplicitAny: oRPC query option factories do not satisfy Solid Query's overloaded public type type QueryOptionFactory<_TData> = () => any; @@ -64,6 +63,7 @@ export type SourceMediaPageProps = { selectedCount?: () => number; onEnterBulkSelectMode?: () => void; persistenceSurface?: SearchPersistenceSurface; + searchHistoryClient: SearchHistoryClient; /** The route owns the presentation surface (legacy, v2, or another host). */ screenComponent: Component; scrollContainerSelector?: string; @@ -71,10 +71,11 @@ export type SourceMediaPageProps = { export function SourceMediaPage(props: SourceMediaPageProps): JSX.Element { const queryClient = useQueryClient(); - const isSearchStateRestored = useCurrentSearchPersistence( - props.mediaSourceId, - { surface: props.persistenceSurface }, - ); + const searchHistory = useSearchHistoryPersistence(props.mediaSourceId, { + client: props.searchHistoryClient, + surface: props.persistenceSurface, + }); + const isSearchStateRestored = searchHistory.isRestored; const tags = createQuery( clientOnlyQueryOptions(props.tagsQueryOptions), @@ -127,8 +128,10 @@ export function SourceMediaPage(props: SourceMediaPageProps): JSX.Element { sortOrder: props.sortOrder, onThumbnailReady: props.onThumbnailReady, isSearchStateRestored, + commitSearchHistory: searchHistory.commitNow, enableVirtualization: props.enableVirtualization, scrollContainerSelector: props.scrollContainerSelector, + historyEntryKey: searchHistory.historyEntryKey, }); const renderActions: SourceMediaScreenProps["renderActions"] = (actions) => ( From 08e76faafd79036d3aa93b256f4a12728770d1e2 Mon Sep 17 00:00:00 2001 From: hmjn Date: Sun, 23 Aug 2026 17:55:37 +0900 Subject: [PATCH 2/3] fix(search): address history review feedback --- apps/server/public/openapi.json | 20 ++--- .../api/routers/search-snapshots-router.ts | 32 +++++++- .../src/tests/e2e/search-history.spec.ts | 33 ++++++++ .../services/search-snapshot-service.test.ts | 27 +++++++ docs/architecture/search-history.md | 2 +- .../contract/search-snapshots.contract.ts | 6 +- packages/core/src/domain/search/history.ts | 37 +++++++++ .../search-snapshot-repository.ts | 2 +- .../hooks/use-search-history-persistence.ts | 81 ++++++++++++++++++- .../ui/src/hooks/use-source-media-page.ts | 2 +- packages/ui/src/search-history-client.ts | 4 +- 11 files changed, 221 insertions(+), 25 deletions(-) diff --git a/apps/server/public/openapi.json b/apps/server/public/openapi.json index 9cfed5c52..f9e3f5003 100644 --- a/apps/server/public/openapi.json +++ b/apps/server/public/openapi.json @@ -10326,7 +10326,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/SearchSnapshot" + "$ref": "#/components/schemas/SafeSearchSnapshot" } } } @@ -10467,7 +10467,10 @@ "sortOrder" ] }, - "SearchSnapshot": { + "SafeSearchSnapshotState": { + "$ref": "#/components/schemas/SearchSnapshotState" + }, + "SafeSearchSnapshot": { "type": "object", "properties": { "id": { @@ -10478,23 +10481,14 @@ "const": 1, "type": "integer" }, - "fingerprint": { - "type": "string" - }, "state": { - "$ref": "#/components/schemas/SearchSnapshotState" - }, - "createdAt": { - "type": "string", - "format": "date-time" + "$ref": "#/components/schemas/SafeSearchSnapshotState" } }, "required": [ "id", "version", - "fingerprint", - "state", - "createdAt" + "state" ] } } diff --git a/apps/server/src/infrastructure/api/routers/search-snapshots-router.ts b/apps/server/src/infrastructure/api/routers/search-snapshots-router.ts index ff45f7b26..482e43dc4 100644 --- a/apps/server/src/infrastructure/api/routers/search-snapshots-router.ts +++ b/apps/server/src/infrastructure/api/routers/search-snapshots-router.ts @@ -1,13 +1,43 @@ import { implement } from "@orpc/server"; import { searchSnapshotsContract } from "@solid-imager/core/domain/contract/search-snapshots.contract"; +import type { SearchSnapshot } from "@solid-imager/core/domain/search/history"; +import { safeSearchSnapshotSchema } from "@solid-imager/core/domain/search/history"; import { SearchSnapshotService } from "~/infrastructure/services/search-snapshot-service"; const os = implement(searchSnapshotsContract); +function toSafeSearchSnapshot(snapshot: SearchSnapshot) { + const { state } = snapshot; + return safeSearchSnapshotSchema.parse({ + id: snapshot.id, + version: snapshot.version, + state: { + mode: state.mode, + searchQuery: state.searchQuery, + selectedTags: state.selectedTags, + excludeTags: state.excludeTags, + tagMode: state.tagMode, + selectedSource: state.selectedSource, + selectedProjects: state.selectedProjects, + selectedIps: state.selectedIps, + selectedCharacters: state.selectedCharacters, + selectedAuthors: state.selectedAuthors, + advancedCondition: state.advancedCondition, + similarityAnchorMediaId: state.similarityAnchorMediaId, + similarityTopK: state.similarityTopK, + limit: state.limit, + sortBy: state.sortBy, + sortOrder: state.sortOrder, + }, + }); +} + export const searchSnapshotsRouter = os.router({ capture: os.capture.handler(async ({ input }) => { const snapshot = await SearchSnapshotService.capture(input.state); return { id: snapshot.id }; }), - get: os.get.handler(async ({ input }) => SearchSnapshotService.get(input.id)), + get: os.get.handler(async ({ input }) => + toSafeSearchSnapshot(await SearchSnapshotService.get(input.id)), + ), }); diff --git a/apps/server/src/tests/e2e/search-history.spec.ts b/apps/server/src/tests/e2e/search-history.spec.ts index 01632e297..70406442d 100644 --- a/apps/server/src/tests/e2e/search-history.spec.ts +++ b/apps/server/src/tests/e2e/search-history.spec.ts @@ -75,3 +75,36 @@ test("search results remain traversable across detail navigation", async ({ page.getByRole("link", { name: new RegExp(E2E_SIMILAR_FILE_NAME) }), ).toBeVisible(); }); + +test("flushes pending search state before immediate navigation", async ({ + page, +}) => { + await page.goto("/search"); + await expect( + page.getByRole("heading", { name: "メディア検索", exact: true }), + ).toBeVisible(); + await waitForAppHydration(page); + + const fileNameInput = page.getByPlaceholder("ファイル名を入力..."); + const searchButton = page.getByRole("button", { name: "検索", exact: true }); + await fileNameInput.fill(E2E_PRIMARY_FILE_NAME); + await searchButton.click(); + await expect(page).toHaveURL(snapshotUrl); + + const primaryResult = page.getByRole("link", { + name: new RegExp(E2E_PRIMARY_FILE_NAME), + }); + await expect(primaryResult).toBeVisible(); + + // Leave the current search before the debounce timer can commit the edit. + await fileNameInput.fill(E2E_SIMILAR_FILE_NAME); + await page.getByRole("link", { name: "Home", exact: true }).click(); + await expect(page).toHaveURL(/\/$/); + + await page.goBack(); + await waitForAppHydration(page); + await expect(page).toHaveURL(/\/search(?:\?search=[0-9a-f-]{36})?$/); + await expect(page.getByPlaceholder("ファイル名を入力...")).toHaveValue( + E2E_SIMILAR_FILE_NAME, + ); +}); diff --git a/apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts b/apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts index a402096c0..81e2c2e6b 100644 --- a/apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts +++ b/apps/server/src/tests/unit/application/services/search-snapshot-service.test.ts @@ -1,3 +1,4 @@ +import { fingerprintState } from "@solid-imager/application/services/search-snapshot-service"; import { ResourceNotFoundError } from "@solid-imager/core/domain/errors"; import type { SearchSnapshotRepository } from "@solid-imager/core/domain/repositories/search-snapshot-repository"; import { @@ -62,6 +63,32 @@ describe("SearchSnapshotService", () => { expect(secondFingerprint).toBe(firstFingerprint); }); + it("generates the same fingerprint when state keys are inserted in a different order", () => { + const reorderedState = { + sortOrder: snapshotState.sortOrder, + sortBy: snapshotState.sortBy, + limit: snapshotState.limit, + similarityTopK: snapshotState.similarityTopK, + similarityAnchorMediaId: snapshotState.similarityAnchorMediaId, + advancedCondition: snapshotState.advancedCondition, + selectedAuthors: snapshotState.selectedAuthors, + selectedCharacters: snapshotState.selectedCharacters, + selectedIps: snapshotState.selectedIps, + selectedProjects: snapshotState.selectedProjects, + selectedSource: snapshotState.selectedSource, + tagMode: snapshotState.tagMode, + excludeTags: snapshotState.excludeTags, + selectedTags: snapshotState.selectedTags, + searchQuery: snapshotState.searchQuery, + mode: snapshotState.mode, + } satisfies typeof snapshotState; + + expect(Object.keys(reorderedState)).not.toEqual(Object.keys(snapshotState)); + expect(fingerprintState(reorderedState)).toBe( + fingerprintState(snapshotState), + ); + }); + it("throws a typed not-found error", async () => { vi.mocked(repository.get).mockResolvedValue(null); diff --git a/docs/architecture/search-history.md b/docs/architecture/search-history.md index a8fa4181c..7ca3c17c1 100644 --- a/docs/architecture/search-history.md +++ b/docs/architecture/search-history.md @@ -63,4 +63,4 @@ sequenceDiagram ## 検証 -`apps/server/src/tests/e2e/search-history.spec.ts` は、通常検索と類似検索を別々の履歴エントリにした後、詳細画面から `goBack()` を複数回実行し、各検索結果と direct URL reload が復元されることを確認する。 +`apps/server/src/tests/e2e/search-history.spec.ts` は、2つのファイル名検索を別々の履歴エントリにした後、詳細画面から `goBack()` を複数回実行し、各検索結果と direct URL reload が復元されることを確認する。 diff --git a/packages/core/src/domain/contract/search-snapshots.contract.ts b/packages/core/src/domain/contract/search-snapshots.contract.ts index 0c0abdbb6..33c40a414 100644 --- a/packages/core/src/domain/contract/search-snapshots.contract.ts +++ b/packages/core/src/domain/contract/search-snapshots.contract.ts @@ -3,12 +3,14 @@ import { captureSearchSnapshotRequestSchema, captureSearchSnapshotResponseSchema, getSearchSnapshotRequestSchema, - searchSnapshotSchema, + safeSearchSnapshotSchema, } from "../search/history"; export const searchSnapshotsContract = { capture: oc .input(captureSearchSnapshotRequestSchema) .output(captureSearchSnapshotResponseSchema), - get: oc.input(getSearchSnapshotRequestSchema).output(searchSnapshotSchema), + get: oc + .input(getSearchSnapshotRequestSchema) + .output(safeSearchSnapshotSchema), }; diff --git a/packages/core/src/domain/search/history.ts b/packages/core/src/domain/search/history.ts index 20640ef52..3fe69530f 100644 --- a/packages/core/src/domain/search/history.ts +++ b/packages/core/src/domain/search/history.ts @@ -29,6 +29,43 @@ export const searchSnapshotSchema = z.object({ export type SearchSnapshot = z.infer; +/** + * Explicitly public search state returned by the history API. + * + * Keep this list separate from the persistence schema so adding an internal + * snapshot field cannot implicitly expose it to clients. + */ +export const safeSearchSnapshotStateSchema = searchSnapshotStateSchema.pick({ + mode: true, + searchQuery: true, + selectedTags: true, + excludeTags: true, + tagMode: true, + selectedSource: true, + selectedProjects: true, + selectedIps: true, + selectedCharacters: true, + selectedAuthors: true, + advancedCondition: true, + similarityAnchorMediaId: true, + similarityTopK: true, + limit: true, + sortBy: true, + sortOrder: true, +}); + +export type SafeSearchSnapshotState = z.infer< + typeof safeSearchSnapshotStateSchema +>; + +export const safeSearchSnapshotSchema = z.object({ + id: z.uuid({ version: "v4" }), + version: searchSnapshotVersionSchema, + state: safeSearchSnapshotStateSchema, +}); + +export type SafeSearchSnapshot = z.infer; + export const captureSearchSnapshotRequestSchema = z.object({ state: searchSnapshotStateSchema, }); diff --git a/packages/db/src/repositories/search-snapshot-repository.ts b/packages/db/src/repositories/search-snapshot-repository.ts index a86fa8309..13267c0d8 100644 --- a/packages/db/src/repositories/search-snapshot-repository.ts +++ b/packages/db/src/repositories/search-snapshot-repository.ts @@ -20,7 +20,7 @@ function mapToEntity(row: SearchSnapshotRow): SearchSnapshot { } export function createSearchSnapshotRepository( - getExecutor: (tx?: unknown) => DrizzleExecutor, + getExecutor: () => DrizzleExecutor, ): SearchSnapshotRepository { const getBy = async ( column: typeof searchSnapshots.id | typeof searchSnapshots.fingerprint, diff --git a/packages/ui/src/hooks/use-search-history-persistence.ts b/packages/ui/src/hooks/use-search-history-persistence.ts index 1a82d6986..725f610a6 100644 --- a/packages/ui/src/hooks/use-search-history-persistence.ts +++ b/packages/ui/src/hooks/use-search-history-persistence.ts @@ -1,8 +1,9 @@ import type { - SearchSnapshot, + SafeSearchSnapshot, SearchSnapshotState, } from "@solid-imager/core/domain/search/history"; import { searchSnapshotStateSchema } from "@solid-imager/core/domain/search/history"; +import type { HistoryLocation } from "@tanstack/solid-router"; import { useLocation, useRouter } from "@tanstack/solid-router"; import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"; import { isServer } from "solid-js/web"; @@ -28,6 +29,12 @@ type HistorySnapshotEntry = { state: SearchSnapshotState; }; +type NavigationBlockerArgs = { + currentLocation: Pick; + nextLocation: Pick; + action: "PUSH" | "REPLACE" | "FORWARD" | "BACK" | "GO"; +}; + type SearchHistoryOptions = SearchPersistenceOptions & { client: SearchHistoryClient; }; @@ -122,6 +129,12 @@ function mergeHistoryEntry( }; } +function removeSearchSnapshot(state: unknown): Record { + const nextState = { ...getRecord(state) }; + delete nextState.searchSnapshot; + return nextState; +} + /** * Restores a search state from the URL/history entry and captures settled * result-changing state as browser history entries. @@ -144,6 +157,8 @@ export function useSearchHistoryPersistence( const currentHref = () => router.history.location.href; const currentHistoryState = () => router.history.location.state; + const routePathname = new URL(currentHref(), "http://solid-imager.invalid") + .pathname; const currentEntryKey = () => readHistoryEntryKey(currentHistoryState(), currentHref()); @@ -151,6 +166,7 @@ export function useSearchHistoryPersistence( router.history.replace( createPathWithoutSnapshot(currentHref()), mergeHistoryEntry(currentHistoryState(), entry), + { ignoreBlocker: true }, ); router.history.flush(); return currentEntryKey(); @@ -162,7 +178,13 @@ export function useSearchHistoryPersistence( ) => { try { const response = await options.client.capture(state); - if (currentEntryKey() !== entryKey) return; + if ( + new URL(currentHref(), "http://solid-imager.invalid").pathname !== + routePathname || + currentEntryKey() !== entryKey + ) { + return; + } const currentEntry = parseHistoryEntry(currentHistoryState()); const nextEntry: HistorySnapshotEntry = { version: HISTORY_VERSION, @@ -173,6 +195,7 @@ export function useSearchHistoryPersistence( router.history.replace( path, mergeHistoryEntry(currentHistoryState(), nextEntry), + { ignoreBlocker: true }, ); router.history.flush(); } catch { @@ -200,7 +223,8 @@ export function useSearchHistoryPersistence( snapshotState = localEntry.state; } else if (queryId) { try { - const snapshot: SearchSnapshot = await options.client.get(queryId); + const snapshot: SafeSearchSnapshot = + await options.client.get(queryId); if (generation !== restoreGeneration) return; snapshotState = snapshot.state; } catch { @@ -209,6 +233,7 @@ export function useSearchHistoryPersistence( router.history.replace( createPathWithoutSnapshot(href), getRecord(currentHistoryState()), + { ignoreBlocker: true }, ); router.history.flush(); } @@ -249,7 +274,13 @@ export function useSearchHistoryPersistence( }); const commitCurrentState = () => { - if (!isRestored()) return; + if ( + !isRestored() || + new URL(currentHref(), "http://solid-imager.invalid").pathname !== + routePathname + ) { + return; + } const state = readSnapshotState(); const key = stateKey(state); if (skipNextCommitState === key) { @@ -263,12 +294,52 @@ export function useSearchHistoryPersistence( router.history.push( createPathWithoutSnapshot(currentHref()), mergeHistoryEntry(currentHistoryState(), entry), + { ignoreBlocker: true }, ); router.history.flush(); const pushedKey = currentEntryKey(); void captureForCurrentEntry(pushedKey, state); }; + let navigationCommitTriggered = false; + const disposeNavigationBlocker = isServer + ? undefined + : router.history.block({ + enableBeforeUnload: false, + blockerFn: ({ + currentLocation, + nextLocation, + action, + }: NavigationBlockerArgs) => { + if ( + (action !== "PUSH" && action !== "REPLACE") || + currentLocation.pathname !== routePathname || + nextLocation.pathname === routePathname + ) { + return false; + } + navigationCommitTriggered = true; + commitCurrentState(); + if (action === "REPLACE") { + router.history.replace( + nextLocation.href, + removeSearchSnapshot(nextLocation.state), + { + ignoreBlocker: true, + }, + ); + } else { + router.history.push( + nextLocation.href, + removeSearchSnapshot(nextLocation.state), + { ignoreBlocker: true }, + ); + } + router.history.flush(); + return true; + }, + }); + createEffect(() => { if (isServer || !isRestored()) return; const state = readSnapshotState(); @@ -285,6 +356,8 @@ export function useSearchHistoryPersistence( }); onCleanup(() => { + disposeNavigationBlocker?.(); + if (!navigationCommitTriggered) commitCurrentState(); if (commitTimer) clearTimeout(commitTimer); }); diff --git a/packages/ui/src/hooks/use-source-media-page.ts b/packages/ui/src/hooks/use-source-media-page.ts index ab2916e81..fe75575e1 100644 --- a/packages/ui/src/hooks/use-source-media-page.ts +++ b/packages/ui/src/hooks/use-source-media-page.ts @@ -294,10 +294,10 @@ export function useSourceMediaPage( // --- Search handler --- const handleSearch = () => { + commitSearchHistory?.(); const sourceId = id(); if (sourceId) setScrollPosition(sourceId, 0, historyEntryKey?.()); scrollToPosition(options.scrollContainerSelector, 0); - commitSearchHistory?.(); }; let nextPageFetchInFlight = false; diff --git a/packages/ui/src/search-history-client.ts b/packages/ui/src/search-history-client.ts index 845924209..3d8fc652e 100644 --- a/packages/ui/src/search-history-client.ts +++ b/packages/ui/src/search-history-client.ts @@ -1,12 +1,12 @@ import type { SearchSnapshotsOrpcLike } from "@solid-imager/core/domain/contract/search-snapshots-client"; import type { - SearchSnapshot, + SafeSearchSnapshot, SearchSnapshotState, } from "@solid-imager/core/domain/search/history"; export type SearchHistoryClient = { capture: (state: SearchSnapshotState) => Promise<{ id: string }>; - get: (id: string) => Promise; + get: (id: string) => Promise; }; export function createSearchHistoryClient( From 46f2bb7754361892f547004c475bb6a4ca5cf6aa Mon Sep 17 00:00:00 2001 From: hmjn Date: Sun, 23 Aug 2026 17:58:14 +0900 Subject: [PATCH 3/3] docs(search): align snapshot response name --- docs/architecture/search-history.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/architecture/search-history.md b/docs/architecture/search-history.md index 7ca3c17c1..6d87c2799 100644 --- a/docs/architecture/search-history.md +++ b/docs/architecture/search-history.md @@ -49,7 +49,7 @@ sequenceDiagram | Operation | Input | Output | 用途 | | --- | --- | --- | --- | | `searchSnapshots.capture` | `SearchSnapshotState` | `{ id }` | state を fingerprint 付きで保存し、同じ検索を再利用する | -| `searchSnapshots.get` | UUID | `SearchSnapshot` | 直接 URL、リロード、履歴復元から state を取得する | +| `searchSnapshots.get` | UUID | `SafeSearchSnapshot` | 直接 URL、リロード、履歴復元から公開対象の state を取得する | 保存先は `packages/db/src/schema.ts` の `search_snapshots` テーブルで、`fingerprint` は一意、`state` は JSON、`created_at` には index を持つ。fingerprint は version と state をキー順に安定シリアライズして SHA-256 化するため、保存順に依存しない。