feat(v2): add no-migration feature support - #663
Conversation
📝 WalkthroughWalkthroughV2画面を実データに接続しました。ジョブ管理、AIヘルスチェック、ソース同期状態、メディア件数、リスト表示、メディア詳細の前後移動、Tauriルート変換を追加しました。 Changesgh-stack運用文書
V2機能とAPI拡張
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant V2JobsScreen
participant V2JobsRoute
participant jobsRouter
participant Database
V2JobsScreen->>V2JobsRoute: request refresh or retry
V2JobsRoute->>jobsRouter: call jobs API
jobsRouter->>Database: query or update job
Database-->>jobsRouter: return job DTO data
jobsRouter-->>V2JobsRoute: return jobs response
V2JobsRoute-->>V2JobsScreen: update list and inspector
sequenceDiagram
participant DirectorySyncService
participant RealtimeEventBus
participant useSourcesEvents
participant V2SourceList
DirectorySyncService->>RealtimeEventBus: publish source-sync-status
RealtimeEventBus->>useSourcesEvents: validate and handle event
useSourcesEvents->>V2SourceList: invalidate query and update status
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
de3e087 to
a331c5c
Compare
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (4)
packages/ui/src/screens/v2-jobs-screen.tsx (1)
404-407: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value型アサーションの理由をコメントで示してください。
value as JobFilterは外部ライブラリ境界での最小スコープのアサーションです。コーディングガイドラインは、やむを得ない場合に理由をコメントで示すことを求めています。♻️ 提案する変更
<Tabs - onChange={(value) => setActiveFilter(value as JobFilter)} + // Kobalte の onChange は string を渡す。値は JOB_FILTERS の value に限定される。 + onChange={(value) => setActiveFilter(value as JobFilter)} value={activeFilter()} >コーディングガイドラインに基づく指摘です(「外部ライブラリ境界では……型アサーションがやむを得ない場合は最小スコープに限定して理由をコメントする」)。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/screens/v2-jobs-screen.tsx` around lines 404 - 407, Tabs の onChange コールバックにある value as JobFilter の型アサーション直前へ、外部ライブラリの値が JobFilter として型付けされていないため最小スコープでアサーションしている理由を示すコメントを追加してください。Source: Coding guidelines
packages/ui/src/query-options/jobs-query.ts (1)
7-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueキーのプレフィックスを
all()から派生させてください。
"jobs"のリテラルが3箇所に重複しています。all()を基点にすると、プレフィックスの不整合を防げます。無効化はall()のプレフィックス一致に依存するため、この整合は重要です。♻️ 提案する変更
export const jobsQueryKeys = { all: () => ["jobs"] as const, - list: (input: JobListRequest) => ["jobs", "list", input] as const, - detail: (jobId: string) => ["jobs", "detail", jobId] as const, + list: (input: JobListRequest) => + [...jobsQueryKeys.all(), "list", input] as const, + detail: (jobId: string) => [...jobsQueryKeys.all(), "detail", jobId] as const, };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/ui/src/query-options/jobs-query.ts` around lines 7 - 11, Update jobsQueryKeys so list and detail derive their shared prefix from all() instead of repeating the "jobs" literal, while preserving their existing key shapes and all() prefix-matching behavior..agents/skills/gh-stack/SKILL.md (1)
17-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winmarkdownlint の警告を解消してください。
静的解析が MD040 を十個のコードフェンスで、MD028 を Line 321 で検出しています。ASCII 図とコマンド構文には
text、実行例にはbashなどの言語識別子を付けてください。Line 321 の blockquote 内の空行は削除してください。Also applies to: 84-84, 321-321, 416-416, 453-453, 502-502, 575-575, 629-629, 670-670, 803-803, 839-839
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.agents/skills/gh-stack/SKILL.md at line 17, SKILL.md 内の10個のコードフェンスに適切な言語識別子を追加し、ASCII図とコマンド構文にはtext、実行例にはbashなどを指定してください。あわせて、blockquote内の空行を削除してMD028警告を解消してください。Source: Linters/SAST tools
apps/server/src/application/services/directory-sync-service.ts (1)
27-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
globalThisの型アサーションを除去してください。Line 27 は拡張したグローバル型を型アサーションで作成しています。
declare globalで__SOLID_IMAGER_SOURCE_SYNC_STATES__を宣言し、直接参照してください。型アサーションを維持する場合は、必要な理由をコメントしてください。As per coding guidelines, 「型アサーションがやむを得ない場合は最小スコープに限定して理由をコメントする。」
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/server/src/application/services/directory-sync-service.ts` around lines 27 - 33, Remove the globalThis type assertion around syncStateGlobal and add a declare global declaration for __SOLID_IMAGER_SOURCE_SYNC_STATES__ with type Map<string, SourceSyncStatus> | undefined. Update the sourceSyncStates initialization to reference globalThis.__SOLID_IMAGER_SOURCE_SYNC_STATES__ directly while preserving its existing fallback and assignment behavior.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.agents/skills/gh-stack/SKILL.md:
- Around line 56-57: Update the gh stack submit behavior description in the
relevant numbered guidance to state that omitting --auto opens the full-screen
editor for the PR title, body, and draft/ready status; retain the recommendation
to always use --auto, explaining that it skips this editor.
- Line 322: Update the gh stack sync guidance to state that automation must not
proceed based solely on a successful exit code: in non-interactive divergence,
detect “ℹ Sync aborted” on stderr and stop processing. For flows that continue,
require validating the current stack state with gh stack view --json; direct
resolution toward unstacking and recreating the stack.
In `@apps/server/public/openapi.json`:
- Around line 6826-6831:
OpenAPI生成で、/jobs/list、/jobs/get、/jobs/retryの200レスポンスに具体的なスキーマを設定し、空のanyOf({} と
not
{})を除去してください。jobListResponseSchemaにはjobListResponseSchema、個別ジョブ応答にはjobDtoSchemaの契約を参照させ、生成クライアントがitems、total、ジョブDTOの型を取得できるようにします。
In `@apps/server/src/application/services/directory-sync-service.ts`:
- Around line 168-182: Make sync execution exclusive per source ID in
syncMediaSource by adding a per-mediaSourceId mutex or in-flight Promise guard
before the synchronization work begins. Skip or await duplicate calls according
to the existing flow, and ensure the guard is always released in a finally
block, including errors and early returns.
- Around line 267-271: Prevent internal error details from reaching clients: in
apps/server/src/application/services/directory-sync-service.ts:267-271, update
publishSyncStatus to use a fixed safe directory-sync failure message instead of
error.message, while retaining detailed errors in server logs. In
apps/server/src/infrastructure/api/routers/jobs-router.ts:53-65, map job.error
to an explicitly public error code or fixed safe message when constructing the
Safe Job DTO.
In `@apps/server/src/infrastructure/api/routers/jobs-router.ts`:
- Around line 111-123: Update the retry operation in the jobs router to
atomically update only when the job ID matches and its status is "failed" by
adding that status predicate to the update condition. When no row is returned,
preserve the not-found response for missing jobs and return the appropriate
current-state error for jobs that are no longer failed.
- Around line 72-125: 公開ハンドラの list、get、retry に共有レスポンススキーマを os.output(...)
で設定し、toJobDto の戻り値と list の items/total
を実行時に検証できるようにしてください。既存の入力検証とハンドラの処理内容は維持し、各エンドポイントで適切な共通出力スキーマを再利用してください。
- Around line 111-124: 再試行成功時にジョブ更新イベントが発行されていません。`requeued`
の存在確認後、`toJobDto(requeued)` で作成した成功 DTO を既存のジョブイベント契約で `RealtimeEventBus`
に発行してから返却し、未検出時は現在の `NOT_FOUND` 処理を維持してください。
In `@apps/server/src/routes/v2/jobs.tsx`:
- Around line 20-25: Update the useJobEvents invalidation callback so
high-frequency job-progress events do not invalidate jobsQueryKeys.all() on
every event. Restrict invalidation to job completion/failure events, or throttle
all invalidations to approximately 500ms–1s while preserving timely list
refreshes.
In `@apps/server/src/routes/v2/sources/`$mediaSourceId/$mediaId/index.tsx:
- Around line 51-62: Update navigateToNeighbor so neighbor navigation replaces
the current detail-page history entry by enabling replace: true on navigate.
Preserve the existing neighbor lookup, route parameters, and destination.
In `@apps/tauri/src/routes/v2/`$.tsx:
- Around line 11-16: Update the segment decoding in segments() to safely handle
decodeURIComponent failures, including malformed percent escapes and invalid
UTF-8. Return the original segment whenever decoding throws, so route resolution
can continue and NotFoundScreen remains reachable.
In `@packages/ui/src/hooks/use-job-events.ts`:
- Around line 10-18: Update useJobEvents so enabled is an Accessor<boolean>
matching use-media-source-events.ts, and evaluate enabled() inside its
createEffect guard. Preserve the existing server check and ensure the effect
starts or stops the event subscription reactively when the signal changes.
In `@packages/ui/src/screens/v2-jobs-screen.tsx`:
- Around line 143-200: Add the missing accessibility attributes in JobsTable:
provide a descriptive table caption, add scope="col" to each column header, and
set the row-selection button’s aria-pressed based on whether job.id matches
selectedJobId.
- Around line 56-63: Replace the local formatDate helper in the jobs screen with
the shared formatDate from v2-manager/utils, reusing its Date | string handling
and removing the duplicate dateFormatter definition.
---
Nitpick comments:
In @.agents/skills/gh-stack/SKILL.md:
- Line 17: SKILL.md
内の10個のコードフェンスに適切な言語識別子を追加し、ASCII図とコマンド構文にはtext、実行例にはbashなどを指定してください。あわせて、blockquote内の空行を削除してMD028警告を解消してください。
In `@apps/server/src/application/services/directory-sync-service.ts`:
- Around line 27-33: Remove the globalThis type assertion around syncStateGlobal
and add a declare global declaration for __SOLID_IMAGER_SOURCE_SYNC_STATES__
with type Map<string, SourceSyncStatus> | undefined. Update the sourceSyncStates
initialization to reference globalThis.__SOLID_IMAGER_SOURCE_SYNC_STATES__
directly while preserving its existing fallback and assignment behavior.
In `@packages/ui/src/query-options/jobs-query.ts`:
- Around line 7-11: Update jobsQueryKeys so list and detail derive their shared
prefix from all() instead of repeating the "jobs" literal, while preserving
their existing key shapes and all() prefix-matching behavior.
In `@packages/ui/src/screens/v2-jobs-screen.tsx`:
- Around line 404-407: Tabs の onChange コールバックにある value as JobFilter
の型アサーション直前へ、外部ライブラリの値が JobFilter
として型付けされていないため最小スコープでアサーションしている理由を示すコメントを追加してください。
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 0ddf43c7-d909-42b1-869e-c64e40294d39
📒 Files selected for processing (52)
.agents/skills/gh-stack/SKILL.mdAGENTS.mdREPORT.mdapps/server/public/openapi.jsonapps/server/src/application/services/directory-sync-service.tsapps/server/src/components/media/legacy-media-grid-item.tsxapps/server/src/components/media/v2-media-grid-item.tsxapps/server/src/components/v2/v2-source-list.tsxapps/server/src/hooks/use-media-source-events.tsapps/server/src/infrastructure/api-clients/queries/index.tsapps/server/src/infrastructure/api/routers/ai-router.tsapps/server/src/infrastructure/api/routers/characters-router.tsapps/server/src/infrastructure/api/routers/entity-media-counts.tsapps/server/src/infrastructure/api/routers/ips-router.tsapps/server/src/infrastructure/api/routers/jobs-router.tsapps/server/src/infrastructure/api/routers/projects-router.tsapps/server/src/infrastructure/api/routers/sources-router.tsapps/server/src/routes/sources/$mediaSourceId/components/source-media-page.tsxapps/server/src/routes/sources/$mediaSourceId/components/v2-source-media-page.tsxapps/server/src/routes/v2/config.tsxapps/server/src/routes/v2/jobs.tsxapps/server/src/routes/v2/media-context.tsapps/server/src/routes/v2/search.tsxapps/server/src/routes/v2/sources/$mediaSourceId/$mediaId/index.tsxapps/tauri/src/hooks/use-media-source-events.tsapps/tauri/src/routes/v2.tsxapps/tauri/src/routes/v2/$.tsxpackages/core/src/domain/characters/schemas.tspackages/core/src/domain/contract/ai.contract.tspackages/core/src/domain/contract/jobs.contract.tspackages/core/src/domain/ips/schemas.tspackages/core/src/domain/jobs/schemas.tspackages/core/src/domain/projects/schemas.tspackages/core/src/domain/sources/events.tspackages/core/src/domain/sources/schemas.tspackages/core/src/domain/tagging/schemas.tspackages/ui/src/hooks/use-job-events.tspackages/ui/src/hooks/use-media-source-events.tspackages/ui/src/hooks/use-sources-events.tspackages/ui/src/query-options/index.tspackages/ui/src/query-options/jobs-query.tspackages/ui/src/screens/search-screen.types.tspackages/ui/src/screens/source-media-screen.types.tspackages/ui/src/screens/v2-config-screen.tsxpackages/ui/src/screens/v2-config-state-screen.tsxpackages/ui/src/screens/v2-jobs-screen.tsxpackages/ui/src/screens/v2-manager/entity-panel.tsxpackages/ui/src/screens/v2-search-screen.tsxpackages/ui/src/screens/v2-source-media-screen.tsxpackages/ui/src/source-media-grid.tsxpackages/ui/src/source-media-page.tsxpackages/ui/src/v2/search-toolbar.tsx
| 2. **Always use `--auto` with `gh stack submit`** to auto-generate PR titles. Without `--auto`, `submit` prompts for a title for each new PR. | ||
| 3. **Always use `--json` with `gh stack view`.** Without `--json`, the command launches an interactive TUI that cannot be operated by agents. There is no other appropriate flag — always pass `--json`. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== candidate file =="
git ls-files '.*agents/skills/gh-stack/SKILL.md' || true
echo "== relevant lines =="
if [ -f .agents/skills/gh-stack/SKILL.md ]; then
sed -n '45,75p' .agents/skills/gh-stack/SKILL.md | cat -n
fi
echo "== reference lines =="
if [ -f .agents/skills/gh-stack/SKILL.md ]; then
sed -n '1,20p' .agents/skills/gh-stack/SKILL.md | cat -n
fi
echo "== fetch upstream v0.1.0 README relevant sections =="
python3 - <<'PY'
import urllib.request
url = "https://github.com/github/gh-stack/raw/refs/tags/v0.1.0/README.md"
try:
with urllib.request.urlopen(url, timeout=20) as r:
text = r.read().decode("utf-8")
except Exception as e:
print("FETCH_ERROR", repr(e))
raise SystemExit(0)
for i, line in enumerate(text.splitlines(), 1):
lower = line.lower()
if any(s in lower for s in ["stack submit", "gh stack submit", "stack view", "gh stack view", "editor", "full-screen editor", "pager", "json", "auto"]):
start=max(1,i-3); end=min(len(text.splitlines()),i+6)
print(f"\n--- around line {i} ---")
for no in range(start,end+1):
print(f"{no}: {text.splitlines()[no-1]}")
PYRepository: hmjn023/solid-imager
Length of output: 7098
🌐 Web query:
github gh-stack v0.1.0 README gh stack submit --auto gh stack view --json pager
💡 Result:
The gh-stack GitHub CLI extension provides commands to manage stacked pull requests. Below are the details for the specific commands you requested: gh stack submit --auto The gh stack submit command pushes all branches in the current stack to the remote and creates or updates pull requests on GitHub [1][2]. In an interactive terminal, this command normally opens a full-screen editor [3][2]. By passing the --auto flag (or running in a non-interactive environment like CI), you skip the editor and use automatically generated pull request titles [3][1]. By default, new pull requests created with --auto are set to draft status, unless you also provide the --open flag [3][2]. gh stack view --json The gh stack view command displays the current stack, including branch ordering, pull request links, and the most recent commit information [3][1]. By default, the output is piped through a pager (respecting your GIT_PAGER or PAGER environment variables, or defaulting to less -R) [3][2]. Using the --json flag causes the command to output the stack data as JSON instead of the human-readable table, which is useful for programmatic parsing [3][4]. When using --json, the structured data is written to standard output, while auxiliary status messages are sent to standard error [4].
Citations:
- 1: https://docs.github.com/en/pull-requests/reference/stacked-prs-cli-commands
- 2: https://github.com/github/gh-stack
- 3: https://github.com/github/gh-stack/blob/main/README.md
- 4: https://github.com/github/gh-stack/blob/main/skills/gh-stack/SKILL.md
gh stack submit の挙動説明を更新してください。
Line 58 は、--auto を省略すると対話モードでタイトルを要求すると書いていますが、submit はタイトルだけではなく、フルスクリーン editor でタイトル・本文・draft/ready 状態を編集します。--auto はこの editor をスキップするため、編集内容の説明を削るか、editor 開く挙動に言い換える必要があります。--auto の推奨は保ってください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.agents/skills/gh-stack/SKILL.md around lines 56 - 57, Update the gh stack
submit behavior description in the relevant numbered guidance to state that
omitting --auto opens the full-screen editor for the PR title, body, and
draft/ready status; retain the recommendation to always use --auto, explaining
that it skips this editor.
|
|
||
| > **Note for agents:** In non-interactive environments, the prune prompt is not shown. Use `--prune` explicitly to delete local branches for merged PRs. | ||
|
|
||
| > **Note for agents:** `sync` also mirrors the stack on GitHub locally. If PRs were added to the stack on github.com, their branches are pulled down and appended to the local stack automatically. If the local and remote stacks have **diverged** (you changed the local stack while the remote stack changed differently), sync can only prompt to resolve it in an interactive terminal — in non-interactive environments it aborts the sync (nothing is pushed or updated) and exits successfully with `ℹ Sync aborted`. Resolve a divergence by unstacking and recreating the stack. |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate skill files =="
fd -a 'SKILL\.md$' . || true
echo "== file existence and relevant lines =="
if [ -f ".agents/skills/gh-stack/SKILL.md" ]; then
sed -n '305,335p' .agents/skills/gh-stack/SKILL.md | cat -n
else
echo ".agents/skills/gh-stack/SKILL.md not found"
fi
echo "== search sync/aborted/unstack/view usages in repo =="
rg -n "Sync aborted|stack sync|stack unstack|stack view" . || true
echo "== upstream reference if available via gh-stack public tag/source? =="
python3 - <<'PY'
import urllib.request, urllib.error
url="https://raw.githubusercontent.com/github/gh-stack/refs/tags/v0.1.0/skills/gh-stack/SKILL.md"
try:
txt=urllib.request.urlopen(url, timeout=15).read().decode("utf-8")
for i,line in enumerate(txt.splitlines(),1):
if "Sync aborted" in line or "exit 0" in line or "local stack" in line[:120].lower():
print(f"{url}:{i}:{line[:300]}")
except Exception as e:
print(f"UPSTREAM_FETCH_ERROR: {e}")
PYRepository: hmjn023/solid-imager
Length of output: 2231
🌐 Web query:
github gh-stack skills/gh-stack/SKILL.md Sync aborted exit successfully v0.1.0
💡 Result:
The message "Sync aborted" with an exit status of 0 (successful execution) occurs when running the gh stack sync command in a non-interactive terminal environment [1][2][3]. This happens specifically when the local stack and the remote stack on GitHub have diverged—for example, when you have added or modified branches locally while different pull requests have been added to the same stack on GitHub [2][3][4]. Because the tool cannot automatically reconcile these conflicting changes without human input, it safely aborts the synchronization process to prevent accidental data loss or incorrect updates [1][2][3]. In this scenario: - No changes are pushed to GitHub [1][2][3]. - No existing pull requests are updated [1][2][3]. - The command exits with a code of 0 (success) because it performed a safe, intentional abort rather than failing due to an error [1][3]. To resolve this divergence, you should typically unstack the current local state and recreate the stack [2][3]. The official documentation suggests using gh stack unstack to remove the local stack tracking, and then using gh stack submit to recreate the stack on GitHub [1][5].
Citations:
- 1: https://github.com/github/gh-stack/blob/main/skills/gh-stack/SKILL.md
- 2: https://github.com/github/gh-stack
- 3: https://claudeskills.info/skills/arize-ai/phoenix/gh-stack/
- 4: Sync remote stack with local stack state github/gh-stack#175
- 5: docs(skill): four corrections for non-interactive agent guidance in SKILL.md github/gh-stack#69
gh stack sync の成功終了コードだけで自動化を進めない規則を追加してください。
非対話的な stack divergence では変更が適用されないまま exit 0 になります。ℹ Sync aborted を stderr で検出したら、現在の処理を停止してください。解決は gh stack unstack と再構成を使うため、継続するケースでは gh stack view --json で現在の stack 状態を検証してください。
🧰 Tools
🪛 LanguageTool
[uncategorized] ~322-~322: The official name of this software platform is spelled with a capital “H”.
Context: ...ally. If PRs were added to the stack on github.com, their branches are pulled down and...
(GITHUB)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.agents/skills/gh-stack/SKILL.md at line 322, Update the gh stack sync
guidance to state that automation must not proceed based solely on a successful
exit code: in non-interactive divergence, detect “ℹ Sync aborted” on stderr and
stop processing. For flows that continue, require validating the current stack
state with gh stack view --json; direct resolution toward unstacking and
recreating the stack.
| "anyOf": [ | ||
| {}, | ||
| { | ||
| "not": {} | ||
| } | ||
| ] |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
ジョブAPIの成功レスポンスに具体的なスキーマを出力してください。
Line 6826、Line 6868、Line 6910 の {} はすべてのJSON値に一致します。
このため、/jobs/list、/jobs/get、/jobs/retry の 200 レスポンスは無制約です。
packages/core/src/domain/contract/jobs.contract.ts の jobListResponseSchema と jobDtoSchema の契約をOpenAPIへ出力してください。
このままでは生成クライアントが items、total、ジョブDTOの型を取得できません。
Also applies to: 6868-6873, 6910-6915
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/public/openapi.json` around lines 6826 - 6831,
OpenAPI生成で、/jobs/list、/jobs/get、/jobs/retryの200レスポンスに具体的なスキーマを設定し、空のanyOf({} と
not
{})を除去してください。jobListResponseSchemaにはjobListResponseSchema、個別ジョブ応答にはjobDtoSchemaの契約を参照させ、生成クライアントがitems、total、ジョブDTOの型を取得できるようにします。
| publishSyncStatus(mediaSourceId, "syncing"); | ||
| try { | ||
| const source = await sourceRepo.findById(mediaSourceId); | ||
| if (source?.type !== "local") { | ||
| logger.info( | ||
| { mediaSourceId }, | ||
| "Skipping sync for non-local or missing source", | ||
| ); | ||
| publishSyncStatus( | ||
| mediaSourceId, | ||
| "idle", | ||
| source | ||
| ? "Source type does not support directory sync" | ||
| : "Source not found", | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
同一ソースの同期を排他してください。
Line 168 は状態を更新するだけで、実行中の同期を予約しません。並行した syncMediaSource 呼び出しは両方とも同期処理を開始できます。追加処理と削除処理が同じソースを同時に更新します。
ソース ID ごとの mutex または in-flight Promise を追加し、完了時に必ず解放してください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/application/services/directory-sync-service.ts` around lines
168 - 182, Make sync execution exclusive per source ID in syncMediaSource by
adding a per-mediaSourceId mutex or in-flight Promise guard before the
synchronization work begins. Skip or await duplicate calls according to the
existing flow, and ensure the guard is always released in a finally block,
including errors and early returns.
| publishSyncStatus( | ||
| mediaSourceId, | ||
| "error", | ||
| error instanceof Error ? error.message : "Directory sync failed", | ||
| ); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
内部エラー文をクライアントへ公開しないでください。
バックエンドの生エラーを公開 DTO として送信しています。Error.message と保存済みの jobs.error には、ファイルパス、接続情報、実装詳細が含まれる場合があります。サーバーログには詳細を保持し、クライアントには固定の安全なメッセージまたは公開エラーコードだけを送信してください。
apps/server/src/application/services/directory-sync-service.ts#L267-L271:error.messageの代わりに、同期失敗を示す固定の公開メッセージをイベントへ設定してください。apps/server/src/infrastructure/api/routers/jobs-router.ts#L53-L65:job.errorを Safe Job DTO の公開可能なエラーコードまたは固定メッセージへマッピングしてください。
As per coding guidelines, 「機密情報を含み得るAPIレスポンスは Safe DTO にマッピングし、公開可能な型を明示する。」
📍 Affects 2 files
apps/server/src/application/services/directory-sync-service.ts#L267-L271(this comment)apps/server/src/infrastructure/api/routers/jobs-router.ts#L53-L65
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/application/services/directory-sync-service.ts` around lines
267 - 271, Prevent internal error details from reaching clients: in
apps/server/src/application/services/directory-sync-service.ts:267-271, update
publishSyncStatus to use a fixed safe directory-sync failure message instead of
error.message, while retaining detailed errors in server logs. In
apps/server/src/infrastructure/api/routers/jobs-router.ts:53-65, map job.error
to an explicitly public error code or fixed safe message when constructing the
Safe Job DTO.
Source: Coding guidelines
| const neighbors = () => findV2MediaNeighbors(props.media.id); | ||
| const navigateToNeighbor = (direction: "next" | "previous") => { | ||
| const neighbor = neighbors()[direction]; | ||
| if (!neighbor) return; | ||
| void navigate({ | ||
| params: { | ||
| mediaId: neighbor.id, | ||
| mediaSourceId: neighbor.mediaSourceId, | ||
| }, | ||
| to: "/v2/sources/$mediaSourceId/$mediaId", | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "File candidates:"
fd -a 'index\.tsx$' . | grep 'apps/server/src/routes/v2/sources/.*/index.tsx' || true
file="apps/server/src/routes/v2/sources/\$mediaSourceId/\$mediaId/index.tsx"
if [ -f "$file" ]; then
echo
echo "Outlined symbols in $file:"
ast-grep outline "$file" || true
echo
echo "Relevant lines:"
cat -n "$file" | sed -n '1,120p'
else
echo "File not found: $file"
fi
echo
echo "Search for navigate options and returnToCollection usages:"
rg -n "returnToCollection|navigate\s*\(|replace:\s*true|mediaSourceId|mediaId" apps/server/src -S || trueRepository: hmjn023/solid-imager
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Storage keys around v2:media-return:"
rg -n 'v2:media-return|media-return|SESSION_STORAGE|sessionStorage.setItem|sessionStorage\.setItem' apps/server/src/routes apps/server/src/components apps/server/src -S \
--glob '*.{ts,tsx,js,jsx}' \
--glob '!**/node_modules/**' || true
echo
echo "Routes with return path storage:"
rg -n 'setItem\(["'\'']v2:media-return|setItem \(("|'"'"')v2:media-return' apps/server/src/routes apps/server/src/components apps/server/src -S \
--glob '*.{ts,tsx,js,jsx}' || true
echo
echo "Relevant returnPath usages:"
rg -n 'media-return|returnPath' apps/server/src \
--glob '*.{ts,tsx,js,jsx}' || trueRepository: hmjn023/solid-imager
Length of output: 5308
前後移動で一覧への復帰履歴を維持してください。
navigate は詳細画面ごとに history entry を追加するため、一覧 → A → B の後で戻ると一覧ではなく A を表示します。前後移動には replace: true を指定するか、v2:media-return の return path へ明示的に遷移してください。
修正例
void navigate({
params: {
mediaId: neighbor.id,
mediaSourceId: neighbor.mediaSourceId,
},
+ replace: true,
to: "/v2/sources/$mediaSourceId/$mediaId",
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const neighbors = () => findV2MediaNeighbors(props.media.id); | |
| const navigateToNeighbor = (direction: "next" | "previous") => { | |
| const neighbor = neighbors()[direction]; | |
| if (!neighbor) return; | |
| void navigate({ | |
| params: { | |
| mediaId: neighbor.id, | |
| mediaSourceId: neighbor.mediaSourceId, | |
| }, | |
| to: "/v2/sources/$mediaSourceId/$mediaId", | |
| }); | |
| }; | |
| const neighbors = () => findV2MediaNeighbors(props.media.id); | |
| const navigateToNeighbor = (direction: "next" | "previous") => { | |
| const neighbor = neighbors()[direction]; | |
| if (!neighbor) return; | |
| void navigate({ | |
| params: { | |
| mediaId: neighbor.id, | |
| mediaSourceId: neighbor.mediaSourceId, | |
| }, | |
| replace: true, | |
| to: "/v2/sources/$mediaSourceId/$mediaId", | |
| }); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/server/src/routes/v2/sources/`$mediaSourceId/$mediaId/index.tsx around
lines 51 - 62, Update navigateToNeighbor so neighbor navigation replaces the
current detail-page history entry by enabling replace: true on navigate.
Preserve the existing neighbor lookup, route parameters, and destination.
| const segments = () => | ||
| location() | ||
| .pathname.replace(/^\/v2\/?/, "") | ||
| .split("/") | ||
| .filter(Boolean) | ||
| .map((segment) => decodeURIComponent(segment)); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the referenced catch-all route and nearby error boundary/NotFoundScreen handling.
if [ -f "apps/tauri/src/routes/v2/$.tsx" ]; then
echo "== file =="
wc -l "apps/tauri/src/routes/v2/$.tsx"
echo
echo "== route file =="
cat -n "apps/tauri/src/routes/v2/$.tsx"
else
echo "missing apps/tauri/src/routes/v2/$.tsx"
fd -a '\$.tsx' apps/tauri/src/routes 2>/dev/null | sed 's#^\./##' | head -50 || true
fi
echo
echo "== search NotFoundScreen / ErrorBoundary / decodeURIComponent =="
rg -n "NotFoundScreen|ErrorBoundary|decodeURIComponent|segments" apps/tauri/src || trueRepository: hmjn023/solid-imager
Length of output: 3483
🌐 Web query:
MDN decodeURIComponent URIError invalid percent-encoding behavior
💡 Result:
The JavaScript function decodeURIComponent throws a URIError with the message "malformed URI sequence" when it encounters invalid percent-encoding in the input string [1][2]. Specifically, this error occurs in the following scenarios: 1. Invalid Escape Sequences: The function encounters a '%' character that is not followed by exactly two hexadecimal digits [1][3]. 2. Invalid UTF-8 Encoding: The escape sequences present do not encode a valid UTF-8 character [1][2]. Because decodeURIComponent interprets percent-encoded sequences as UTF-8 bytes, it expects sequences to adhere to the UTF-8 encoding standard [4]. If the bytes do not form a valid UTF-8 sequence, or if they represent incomplete characters (such as an orphan surrogate), the function throws a URIError [2][4]. To handle these cases, developers typically wrap the call in a try...catch block to manage the error gracefully [1][3]. Because this function is strictly designed to decode properly percent-encoded URI components (usually created by encodeURIComponent), it will fail on arbitrary strings containing standalone '%' characters or non-UTF-8 encoded percent sequences [5][6]. If you need to process strings that may contain literal '%' characters that are not part of a valid URI encoding, you must sanitize or encode them (e.g., replacing '%' with '%25') before passing the string to decodeURIComponent [5].
Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent
- 2: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Malformed_URI
- 3: https://github.com/mdn/content/blob/main/files/en-us/web/javascript/reference/global_objects/decodeuricomponent/index.md
- 4: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI
- 5: https://stackoverflow.com/questions/7449588/why-does-decodeuricomponent-lock-up-my-browser
- 6: https://stackoverflow.com/questions/9064536/javascript-decodeuricomponent-malformed-uri-exception
不正な URL エンコードを安全に処理してください。
decodeURIComponent は不正な % エスケープや UTF-8 不正バイト列で URIError を送出します。このルートでは segments() のエラーが Switch の fallback までの間に発生し、NotFoundScreen を表示できません。復号を安全に処理し、復号失敗時は元の未復号値を返してください。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/tauri/src/routes/v2/`$.tsx around lines 11 - 16, Update the segment
decoding in segments() to safely handle decodeURIComponent failures, including
malformed percent escapes and invalid UTF-8. Return the original segment
whenever decoding throws, so route resolution can continue and NotFoundScreen
remains reachable.
| export function useJobEvents( | ||
| openStream: JobEventStreamFactory, | ||
| onEvent: (event: JobEvent) => void, | ||
| enabled = true, | ||
| ): void { | ||
| createEffect(() => { | ||
| if (isServer || !enabled) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# 既存のイベント購読フックの enabled 契約を確認する
fd -t f 'use-.*events\.ts$' | while IFS= read -r file; do
echo "===== $file ====="
rg -n -C 6 'enabled' "$file"
doneRepository: hmjn023/solid-imager
Length of output: 1842
🏁 Script executed:
#!/bin/bash
set -u
echo "===== locate use-job-events files ====="
git ls-files | rg 'packages/ui/src/hooks/use-job-events\.ts$|packages/ui/src/hooks/use-.*events\.ts$' || true
echo "===== inspect useJobEvents implementation ====="
cat -n packages/ui/src/hooks/use-job-events.ts
echo "===== references to useJobEvents ====="
rg -n -C 4 'useJobEvents|openSound' packages || true
echo "===== inspect package ui tsconfig/framework context ====="
for f in package.json packages/ui/package.json packages/ui/tsconfig.json packages/ui/vite.config.* packages/ui/tsconfig.*; do
[ -e "$f" ] && { echo "===== $f ====="; sed -n '1,220p' "$f"; }
done
echo "===== search import context for createEffect signature if node_modules unavailable ====="
rg -n 'type Accessor|createEffect|Function' package.json packages/core packages/ui -g '*.{ts,tsx}' || true
echo "===== deterministic call-expression dependency analysis of event hook createEffect bodies ====="
python3 - <<'PY'
from pathlib import Path
import re
def create_effect_disabled_body_matches(path: Path) -> list[str]:
text = path.read_text()
if 'createEffect' not in text or 'enabled' not in text:
return []
out=[]
for m in re.finditer(r'createEffect\s*\(\s*\(\)\s*=>\s*\{(.*?)\}\s*(?:\),)?\s*\n', text, re.S):
body=m.group(1)
if re.search(r'\byeserver\b|window\.document|navigator\.userAgent|window\.sessionStorage', body):
continue
out.append((m.start(), body))
return out
for path in [Path('packages/ui/src/hooks/use-job-events.ts'), Path('packages/ui/src/hooks/use-batch-job-events.ts'), Path('apps/server/src/hooks/use-batch-job-events.ts')]:
print(f"===== {path} =====")
for pos, body in create_effect_disabled_body_matches(path):
print('position', pos)
print('\n'.join(f'{i+1}: {line}' for i, line in enumerate(body.splitlines())))
print('calls enabled directly?', bool(re.search(r'\benabled\b(?!\s*\()', body)))
print('calls enabled as function in guard?', bool(re.search(r'\benabled\s*\(', body)))
PYRepository: hmjn023/solid-imager
Length of output: 13429
enabled をリアクティブな条件として扱ってください。
enabled は Accessor<boolean> を想定し、createEffect 内で enabled() を返す形にしてください。use-job-events.ts では enable 引数の指定がないため実害はありませんが、このままだとシグナルを渡しても購読の開始と停止が切り替わりません。
use-media-source-events.ts と同じ型契約にすることで、他のイベント購読フックと一貫性が出ます。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/hooks/use-job-events.ts` around lines 10 - 18, Update
useJobEvents so enabled is an Accessor<boolean> matching
use-media-source-events.ts, and evaluate enabled() inside its createEffect
guard. Preserve the existing server check and ensure the effect starts or stops
the event subscription reactively when the signal changes.
| const dateFormatter = new Intl.DateTimeFormat("ja-JP", { | ||
| dateStyle: "medium", | ||
| timeStyle: "short", | ||
| }); | ||
|
|
||
| function formatDate(value: Date): string { | ||
| return dateFormatter.format(value); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# jobDtoSchema の日時フィールド定義を確認する
fd -t f 'schemas.ts$' -p 'domain/jobs' --exec cat -n
# UI 内の formatDate 実装の重複を確認する
rg -nP --type=tsx --type=ts -C 4 'function formatDate'Repository: hmjn023/solid-imager
Length of output: 289
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repo files around ui and relevant package/dirs:"
git ls-files | sed -n '1,120p'
echo
echo "Find v2-jobs-screen:"
fd -a 'v2-jobs-screen\.tsx$' . || true
echo
echo "Find entity-panel:"
fd -a 'entity-panel\.tsx$' . || true
echo
echo "Find schema files with jobs:"
fd -t f 'schemas\.ts$|.*schema.*\.ts$' . | sed -n '1,200p'Repository: hmjn023/solid-imager
Length of output: 10141
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package manifests / deps for zod/orpc:"
fd -t f '(^package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$|bun\.lockb$|bun\.lock$)' . -d 3 --max-count 80 --exec sh -c 'echo "--- $1"; rg -n "\"(zod|`@orpc`|orpc)\"|zod|`@orpc`|orpc" "$1" || true' sh {} \;
echo
echo "Jobs domain schema:"
cat -n packages/core/src/domain/jobs/schemas.ts | sed -n '1,220p'
echo
echo "Relevant UI files outline:"
ast-grep outline packages/ui/src/screens/v2-jobs-screen.tsx --view compact || true
ast-grep outline packages/ui/src/screens/v2-manager/entity-panel.tsx --view compact || true
echo
echo "formatDate occurrences:"
grep -RInE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build 'function formatDate|const formatDate|formatDate\s*=' packages/ui/src app || true
echo
echo "v2-jobs-screen relevant section:"
cat -n packages/ui/src/screens/v2-jobs-screen.tsx | sed -n '1,140p'
echo
echo "entity-panel relevant section if formatDate appears:"
cat -n packages/ui/src/screens/v2-manager/entity-panel.tsx | sed -n '1,180p'Repository: hmjn023/solid-imager
Length of output: 423
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package manifests / deps for zod/orpc:"
fd -t f '(^package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$|bun\.lockb$|bun\.lock$)' . -d 3 --max-results 80 --exec sh -c 'echo "--- $1"; rg -n "\"(zod|`@orpc`|orpc)\"|zod|`@orpc`|orpc" "$1" || true' sh {} \;
echo
echo "Jobs domain schema:"
cat -n packages/core/src/domain/jobs/schemas.ts | sed -n '1,220p'
echo
echo "Relevant UI files outline:"
ast-grep outline packages/ui/src/screens/v2-jobs-screen.tsx --view compact || true
ast-grep outline packages/ui/src/screens/v2-manager/entity-panel.tsx --view compact || true
echo
echo "formatDate occurrences:"
grep -RInE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build 'function formatDate|const formatDate|formatDate\s*=' packages/ui/src app || true
echo
echo "v2-jobs-screen relevant section:"
cat -n packages/ui/src/screens/v2-jobs-screen.tsx | sed -n '1,180p'
echo
echo "entity-panel relevant section:"
cat -n packages/ui/src/screens/v2-manager/entity-panel.tsx | sed -n '1,220p'Repository: hmjn023/solid-imager
Length of output: 449
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package manifests / deps for zod/orpc:"
while IFS= read -r -d '' file; do
echo "--- $file"
rg -n "\"(zod|`@orpc`|orpc)\"|zod|`@orpc`|orpc" "$file" || true
done < <(fd -t f '(^package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$|bun\.lockb$|bun\.lock$)' . -d 3 -x 'true' 2>/dev/null | head -n 80 | tr '\n' '\0')
echo
echo "Jobs domain schema:"
cat -n packages/core/src/domain/jobs/schemas.ts | sed -n '1,240p'
echo
echo "Relevant UI files outline:"
ast-grep outline packages/ui/src/screens/v2-jobs-screen.tsx --view compact || true
ast-grep outline packages/ui/src/screens/v2-manager/entity-panel.tsx --view compact || true
echo
echo "formatDate occurrences:"
grep -RInE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build 'function formatDate|const formatDate|formatDate\s*=' packages/ui/src app || true
echo
echo "v2-jobs-screen relevant section:"
cat -n packages/ui/src/screens/v2-jobs-screen.tsx | sed -n '1,200p'
echo
echo "entity-panel relevant section:"
cat -n packages/ui/src/screens/v2-manager/entity-panel.tsx | sed -n '1,240p'Repository: hmjn023/solid-imager
Length of output: 249
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Package manifests / deps for zod/orpc:"
for file in $(fd -t f '(^package\.json$|package-lock\.json$|pnpm-lock\.yaml$|yarn\.lock$|bun\.lockb$|bun\.lock$)' . -d 3 | head -n 80); do
echo "--- $file"
rg -n "\"(zod|`@orpc`|orpc)\"|zod|`@orpc`|orpc" "$file" || true
done
echo
echo "Jobs domain schema:"
cat -n packages/core/src/domain/jobs/schemas.ts | sed -n '1,280p'
echo
echo "Relevant UI files outline:"
ast-grep outline packages/ui/src/screens/v2-jobs-screen.tsx --view compact || true
ast-grep outline packages/ui/src/screens/v2-manager/entity-panel.tsx --view compact || true
echo
echo "formatDate occurrences:"
grep -RInE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build 'function formatDate|const formatDate|formatDate\s*=' packages/ui/src app || true
echo
echo "v2-jobs-screen relevant section:"
cat -n packages/ui/src/screens/v2-jobs-screen.tsx | sed -n '1,220p'
echo
echo "entity-panel relevant section:"
cat -n packages/ui/src/screens/v2-manager/entity-panel.tsx | sed -n '1,260p'Repository: hmjn023/solid-imager
Length of output: 36514
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "packages/client contract/client setup:"
for file in packages/client/src/server-routes.ts packages/client/src/index.ts packages/client/src/client.ts packages/client/src/index.ts packages/client/src/*; do
if [ -f "$file" ]; then
echo "--- $file"
rg -n "`@solid-imager/core`|zod|date|coerce|fromSchema|createORPC|Date|formatDate" "$file" || true
fi
done
echo
echo "Search for formatDate in client/core:"
grep -RInE --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=build 'formatDate|formatDate|Date' packages/client packages/core packages/ui/src | sed -n '1,220p'
echo
echo "packages/client relevant files containing jobDtoSchema:"
rg -n "jobDtoSchema|jobListResponseSchema|useManagerPage|`@solid-imager/core`" packages/client packages/ui/src | sed -n '1,240p'
echo
echo "packages/client source files:"
fd -t f . packages/client -d 3 | sed -n '1,200p'Repository: hmjn023/solid-imager
Length of output: 23546
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "packages/client/src/create-client.ts:"
cat -n packages/client/src/create-client.ts | sed -n '1,260p'
echo
echo "packages/client/src/query-options/jobs-query.ts:"
cat -n packages/ui/src/query-options/jobs-query.ts | sed -n '1,220p'
echo
echo "packages/ui/src/screens/v2-jobs-screen.tsx usages:"
cat -n packages/ui/src/screens/v2-jobs-screen.tsx | sed -n '200,320p'
echo
echo "packages/ui/src/screens/v2-manager/utils.ts:"
cat -n packages/ui/src/screens/v2-manager/utils.ts | sed -n '1,90p'
echo
echo "packages/ui/src/v2/collection-inspector.tsx:"
cat -n packages/ui/src/v2/collection-inspector.tsx | sed -n '1,90p'Repository: hmjn023/solid-imager
Length of output: 14294
🌐 Web query:
@orpc client zod date coerce schema deserialize from JSON Date object format
💡 Result:
In oRPC, handling the deserialization of date strings into Date objects is typically managed by either the RPC Serializer (for internal oRPC communication) or the Smart Coercion Plugin (for OpenAPI/external interfaces). 1. RPC Serializer (Internal oRPC) The oRPC RPCSerializer natively handles Date objects by serializing them to ISO strings and deserializing them back into Date instances automatically [1]. When using the default serializer, you do not need to manually configure Zod schemas to handle this transformation, as the serializer maintains type fidelity across the network boundary [1]. 2. Smart Coercion (OpenAPI/External) When working with external interfaces (like OpenAPI) where date-time values arrive as JSON strings, you can use the ZodSmartCoercionPlugin [2][3]. This plugin automates the conversion of valid ISO date strings into Date objects without requiring you to manually modify your Zod schemas with z.transform or z.coerce [2]. It is designed to work with Zod and supports standard ISO date-time formats (e.g., 2024-11-27T00:00:00.000Z) [2][3]. 3. Standard Zod Approach If you are not using oRPC's specific plugins and need to handle standard JSON input: - Use z.coerce.date in your Zod schema to automatically convert input strings to Date objects using the Date constructor [4][5]. - For more robust handling, especially when ensuring the input is a valid ISO string before converting, you can chain a validation step: z.string.pipe(z.coerce.date) [6]. Summary Recommendation - If using standard oRPC Client-to-Server communication: Rely on the built-in RPC Serializer [1]. - If processing external/OpenAPI JSON input: Use the Zod Smart Coercion Plugin for seamless, automatic conversion [2][3]. - If working with vanilla Zod/other frameworks: Use z.coerce.date [4].
Citations:
- 1: https://v2.orpc.dev/docs/rpc/serializer
- 2: https://orpc.dev/docs/openapi/plugins/zod-smart-coercion
- 3: https://v2.orpc.dev/docs/plugins/smart-coercion
- 4: https://mintlify.com/colinhacks/zod/api/utilities/coerce
- 5: https://github.com/colinhacks/zod/blob/c7805073/packages/docs/content/api.mdx
- 6: https://stackoverflow.com/questions/75157299/how-can-zod-date-type-accept-iso-date-strings
formatDate は Date を前提としており、共有化も検討してください。
jobDtoSchema.createdAt と updatedAt は z.coerce.date() で Date にパースされ、oRPC シリアライザも日時を Date で渡す前提です。既存の packages/ui/src/screens/v2-manager/utils.ts::formatDate も Date | string 対応で Date を扱えるため、v2-jobs-screen.tsx::formatDate は同じヘルパーに统一できます。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/screens/v2-jobs-screen.tsx` around lines 56 - 63, Replace the
local formatDate helper in the jobs screen with the shared formatDate from
v2-manager/utils, reusing its Date | string handling and removing the duplicate
dateFormatter definition.
| function JobsTable(props: { | ||
| jobs: JobDto[]; | ||
| onSelect: (job: JobDto) => void; | ||
| selectedJobId: string | null; | ||
| }) { | ||
| return ( | ||
| <div class="overflow-hidden rounded-md border border-[var(--v2-border)] bg-[var(--v2-surface)]"> | ||
| <div class="overflow-x-auto"> | ||
| <table class="w-full min-w-[48rem] text-left text-sm"> | ||
| <thead class="border-[var(--v2-border)] border-b bg-[var(--v2-surface-muted)] text-xs uppercase tracking-wide"> | ||
| <tr> | ||
| <th class="px-4 py-3 font-medium">Type</th> | ||
| <th class="px-4 py-3 font-medium">Status</th> | ||
| <th class="px-4 py-3 font-medium">Progress</th> | ||
| <th class="px-4 py-3 font-medium">Updated</th> | ||
| </tr> | ||
| </thead> | ||
| <tbody class="divide-y divide-[var(--v2-border)]"> | ||
| <For each={props.jobs}> | ||
| {(job) => ( | ||
| <tr | ||
| class={ | ||
| props.selectedJobId === job.id | ||
| ? "bg-[var(--v2-primary-soft)]" | ||
| : "" | ||
| } | ||
| data-selected={props.selectedJobId === job.id} | ||
| > | ||
| <td class="px-2 py-2"> | ||
| <button | ||
| class="w-full rounded px-2 py-2 text-left font-medium text-[var(--v2-text)] hover:bg-[var(--v2-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--v2-primary)]" | ||
| onClick={() => props.onSelect(job)} | ||
| type="button" | ||
| > | ||
| <div>{jobTypeLabel(job.type)}</div> | ||
| <div class="mt-0.5 font-normal text-[var(--v2-text-muted)] text-xs"> | ||
| {job.id.slice(0, 8)} | ||
| </div> | ||
| </button> | ||
| </td> | ||
| <td class="px-4 py-3"> | ||
| <JobStatusBadge status={job.status} /> | ||
| </td> | ||
| <td class="px-4 py-3"> | ||
| <JobProgress progress={job.progress} /> | ||
| </td> | ||
| <td class="px-4 py-3 whitespace-nowrap text-[var(--v2-text-secondary)]"> | ||
| {formatDate(job.updatedAt)} | ||
| </td> | ||
| </tr> | ||
| )} | ||
| </For> | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
テーブルのアクセシビリティ属性を追加してください。
同じリポジトリの packages/ui/src/screens/v2-manager/entity-panel.tsx は caption、scope="col"、行ボタンの aria-pressed を持ちます。このテーブルには3つとも不足しています。スクリーンリーダーの利用者は、列見出しの関連付けと選択状態を得られません。
♻️ 提案する変更
<table class="w-full min-w-[48rem] text-left text-sm">
+ <caption class="sr-only">
+ ジョブの一覧。{props.jobs.length}件。
+ </caption>
<thead class="border-[var(--v2-border)] border-b bg-[var(--v2-surface-muted)] text-xs uppercase tracking-wide">
<tr>
- <th class="px-4 py-3 font-medium">Type</th>
- <th class="px-4 py-3 font-medium">Status</th>
- <th class="px-4 py-3 font-medium">Progress</th>
- <th class="px-4 py-3 font-medium">Updated</th>
+ <th class="px-4 py-3 font-medium" scope="col">Type</th>
+ <th class="px-4 py-3 font-medium" scope="col">Status</th>
+ <th class="px-4 py-3 font-medium" scope="col">Progress</th>
+ <th class="px-4 py-3 font-medium" scope="col">Updated</th>
</tr>
</thead> <td class="px-2 py-2">
<button
+ aria-pressed={props.selectedJobId === job.id}
class="w-full rounded px-2 py-2 text-left font-medium text-[var(--v2-text)] hover:bg-[var(--v2-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--v2-primary)]"
onClick={() => props.onSelect(job)}
type="button"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function JobsTable(props: { | |
| jobs: JobDto[]; | |
| onSelect: (job: JobDto) => void; | |
| selectedJobId: string | null; | |
| }) { | |
| return ( | |
| <div class="overflow-hidden rounded-md border border-[var(--v2-border)] bg-[var(--v2-surface)]"> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full min-w-[48rem] text-left text-sm"> | |
| <thead class="border-[var(--v2-border)] border-b bg-[var(--v2-surface-muted)] text-xs uppercase tracking-wide"> | |
| <tr> | |
| <th class="px-4 py-3 font-medium">Type</th> | |
| <th class="px-4 py-3 font-medium">Status</th> | |
| <th class="px-4 py-3 font-medium">Progress</th> | |
| <th class="px-4 py-3 font-medium">Updated</th> | |
| </tr> | |
| </thead> | |
| <tbody class="divide-y divide-[var(--v2-border)]"> | |
| <For each={props.jobs}> | |
| {(job) => ( | |
| <tr | |
| class={ | |
| props.selectedJobId === job.id | |
| ? "bg-[var(--v2-primary-soft)]" | |
| : "" | |
| } | |
| data-selected={props.selectedJobId === job.id} | |
| > | |
| <td class="px-2 py-2"> | |
| <button | |
| class="w-full rounded px-2 py-2 text-left font-medium text-[var(--v2-text)] hover:bg-[var(--v2-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--v2-primary)]" | |
| onClick={() => props.onSelect(job)} | |
| type="button" | |
| > | |
| <div>{jobTypeLabel(job.type)}</div> | |
| <div class="mt-0.5 font-normal text-[var(--v2-text-muted)] text-xs"> | |
| {job.id.slice(0, 8)} | |
| </div> | |
| </button> | |
| </td> | |
| <td class="px-4 py-3"> | |
| <JobStatusBadge status={job.status} /> | |
| </td> | |
| <td class="px-4 py-3"> | |
| <JobProgress progress={job.progress} /> | |
| </td> | |
| <td class="px-4 py-3 whitespace-nowrap text-[var(--v2-text-secondary)]"> | |
| {formatDate(job.updatedAt)} | |
| </td> | |
| </tr> | |
| )} | |
| </For> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function JobsTable(props: { | |
| jobs: JobDto[]; | |
| onSelect: (job: JobDto) => void; | |
| selectedJobId: string | null; | |
| }) { | |
| return ( | |
| <div class="overflow-hidden rounded-md border border-[var(--v2-border)] bg-[var(--v2-surface)]"> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full min-w-[48rem] text-left text-sm"> | |
| <caption class="sr-only"> | |
| ジョブの一覧。{props.jobs.length}件。 | |
| </caption> | |
| <thead class="border-[var(--v2-border)] border-b bg-[var(--v2-surface-muted)] text-xs uppercase tracking-wide"> | |
| <tr> | |
| <th class="px-4 py-3 font-medium" scope="col">Type</th> | |
| <th class="px-4 py-3 font-medium" scope="col">Status</th> | |
| <th class="px-4 py-3 font-medium" scope="col">Progress</th> | |
| <th class="px-4 py-3 font-medium" scope="col">Updated</th> | |
| </tr> | |
| </thead> | |
| <tbody class="divide-y divide-[var(--v2-border)]"> | |
| <For each={props.jobs}> | |
| {(job) => ( | |
| <tr | |
| class={ | |
| props.selectedJobId === job.id | |
| ? "bg-[var(--v2-primary-soft)]" | |
| : "" | |
| } | |
| data-selected={props.selectedJobId === job.id} | |
| > | |
| <td class="px-2 py-2"> | |
| <button | |
| aria-pressed={props.selectedJobId === job.id} | |
| class="w-full rounded px-2 py-2 text-left font-medium text-[var(--v2-text)] hover:bg-[var(--v2-surface-muted)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--v2-primary)]" | |
| onClick={() => props.onSelect(job)} | |
| type="button" | |
| > | |
| <div>{jobTypeLabel(job.type)}</div> | |
| <div class="mt-0.5 font-normal text-[var(--v2-text-muted)] text-xs"> | |
| {job.id.slice(0, 8)} | |
| </div> | |
| </button> | |
| </td> | |
| <td class="px-4 py-3"> | |
| <JobStatusBadge status={job.status} /> | |
| </td> | |
| <td class="px-4 py-3"> | |
| <JobProgress progress={job.progress} /> | |
| </td> | |
| <td class="px-4 py-3 whitespace-nowrap text-[var(--v2-text-secondary)]"> | |
| {formatDate(job.updatedAt)} | |
| </td> | |
| </tr> | |
| )} | |
| </For> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ui/src/screens/v2-jobs-screen.tsx` around lines 143 - 200, Add the
missing accessibility attributes in JobsTable: provide a descriptive table
caption, add scope="col" to each column header, and set the row-selection
button’s aria-pressed based on whether job.id matches selectedJobId.
概要
REPORT.mdで不足していた機能のうち、DB migrationなしで自然に提供できる機能をV2へ接続します。このPRは既存のperf/v2-media-gallery-performance向けPR #662に積み上げています。
変更内容
未対応
検証
Summary by CodeRabbit
新機能
ドキュメント