Phase 4: 検索アーキテクチャ - #4
Merged
Merged
Conversation
README fetch TTL (24h) tracking requires its own timestamp field, separate from lastReleaseCheckedAt. Adds an optional Date field with a lightweight-migration-friendly nil default. Phase4 prep task 1/6. See docs/progress.md Phase4 and the approved plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CacheRefreshScheduler needs to persist which category was last shown so it can do a single lightweight refresh of it on launch. Category now conforms to Defaults.Serializable so it can be stored via the Defaults package. Read-only usage for now (Phase4). The write side (updating the key when a user selects a category) is added in Phase5's Discovery UI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… search Adds the core Phase4 search flow: query input is debounced 300ms, an identical query sent within the last 5s is not resent, and the cache is shown immediately while an API search runs in the background to reconcile it. NoiseFilter (dmg/zip asset check) is applied at the list stage per the user's explicit choice, even though this costs one Releases API call per result. Time-dependent logic (debounce/dedup) is tested via an injected Clock protocol rather than real sleeps: SearchViewModel is generic over ClockType, with a ContinuousClock convenience init for production and a hand-rolled ManualClock for tests (Tests/CairnTests/Features/Search/ManualClock.swift) that advances time on demand. MockGitHubClient is an actor to safely record call counts/queries across the @mainactor view model boundary. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…fier CacheRefreshScheduler needs to reclassify a CachedRepository (a SwiftData model) after fetching its README, but it doesn't have a Repository (API response struct) to hand to the existing classify(repository:readme:) method. Adds a classify(topics:name:readme:) overload that the existing method now delegates to, so both call sites share the same scoring logic. CategoryClassifying is the only conformer of its own protocol today, so adding the new requirement is not a breaking change in practice. Existing CategoryClassifierTests pass unchanged; one new test covers the overload directly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…validation Implements the other half of Phase4: a one-shot lightweight refresh of the last top category on launch (read-only against Defaults.lastTopCategory; the write side lands with Phase5's Discovery UI), plus independent TTL-gated revalidation of Releases (1h) and README (24h) for a repository's detail view. A failed README fetch does not update its TTL or touch the existing classification, so the next visit can retry rather than being stuck with a stale-but-fresh-looking category for 24h. Extracts the GitHub search query construction (repo-language filter) shared by SearchViewModel and this scheduler into GitHubSearchQueryBuilder, since SearchViewModel's version was tied to its generic ClockType for no reason. CacheRefreshSchedulerTests cover the launch refresh, the no-op when no category is set, and TTL boundary behavior for both Releases and README using a fixed now() closure (same injection pattern as AuthenticationState). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the minimal call site for the launch-time top-category refresh, without touching CairnEnvironment's structure (its full DI expansion is Phase5's job). ContentView's .task builds a GitHubClient/CacheRefreshScheduler on the spot and calls refreshTopCategoryOnLaunch() once. Verified via 'swift run Cairn' that the app still launches without crashing with this wired in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Checks off the Phase4 checklist in docs/progress.md and records the design decisions confirmed with the user during planning (DI approach, lastReadmeFetchedAt field, NoiseFilter applied at list stage, README failure TTL handling, launch-refresh wiring) plus a summary of the added test coverage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SearchViewModel: デバウンス300ms・直近同一クエリ5秒以内は再送なし・stale-while-revalidate(キャッシュ即時反映→バックグラウンドAPI検索→ノイズ除去/分類→キャッシュ反映)を実装CacheRefreshScheduler: 起動時に前回トップカテゴリのみ1回軽量更新、詳細画面のReleases(TTL1h)/README(TTL24h)遅延再検証CachedRepositoryにlastReadmeFetchedAt追加、Defaultsパッケージ初導入(前回トップカテゴリ永続化、読み出しのみ・書き込みはPhase5)CategoryClassifierにclassify(topics:name:readme:)オーバーロードを追加(CacheRefreshSchedulerからのSwiftDataモデル経由の再分類用)Clock注入・固定nowクロージャで実待機なしにテストTest plan
swift build成功swift test(79テスト全通過、複数回実行してflakinessなし確認済み)just format/swiftlintクリーンswift run Cairnで起動クラッシュなしを確認詳細な設計判断(NoiseFilter適用タイミング、README失敗時のTTL扱い等、ユーザー確認済み事項)は
docs/progress.mdのPhase4セクション参照。🤖 Generated with Claude Code
https://claude.ai/code/session_01Wqgz7yinLxemFVe6f3NVCg