Conversation
notecli rev を 1f68a91 (V6 note_timelines) に bump し、cross-repo 影響の
全 18 項目を適用する。
backend:
- fetch 系 7 コマンド + api_get_timeline を ingest_notes + TimelineKey に置換
(キー手組みの format! を型レベルで排除)
- QueryKey の antenna/channel/role/mentions kind を
Timeline { account_id, key: canonical } に折り畳み (dedup の二重表現を解消)。
query_open は削除 (無検証キーの裏口・フロント呼び出しゼロ)
- 購読 4 コマンドを subscribe_notes(TimelineKey) に統合
- キャッシュ整合フック: unfavorite / clip 除去 → remove_membership、
antenna 更新 / list メンバー除去 → clear_timeline、
エンティティ削除の後始末用に api_clear_timeline_cache を新設
- api_verify_notes: NO_SUCH_NOTE 確認時のみ miss 判定する
VerifyNotesResult { verified, missing } に変更 — 復帰直後の通信エラーで
キャッシュを恒久削除する誤爆 (原症状「復帰時に薄くなる」の一因) を修正
- api_delete_cached_note に account_id、api_get_cached_timeline_before に
beforeNoteId (keyset cursor ペア)
- cache 系長時間コマンドを spawn_blocking 化、DB open 失敗時に
nd:backend-fatal を emit
front:
- キー手組みを src/utils/columnCacheKey.ts に集約し既存バグ 4 件を修正
(list snapshot キー / specified snapshot キー / role ミラー欠落 /
tl 未設定時のミラー null)
- guest TL 強制変換を deckStore.updateColumn で永続化
- before 遡り 2 箇所を (createdAt, id) の同一ソースペア渡しに
- useNoteList の deleted イベントに accountId 注入
- verify-purge を missing のみ削除に追随 (+ 症状起点テスト)
- エンティティ削除成功後に apiClearTimelineCache (死にバケット掃除)
- cacheEviction に perTimelineLimit、bindings.ts / openapi.json 再生成
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat: notecli 実体/所属分離 (V6) への追随
package.json / src-tauri/Cargo.toml / tauri.conf.json / Cargo.lock / openapi.json を 1.40.0 に同期。あわせて notecli の rev を v0.8.1 (272edae) に更新し、v0.8.0 のレビュー指摘修正 (TimelineKey の path traversal 対策・per-timeline トリムの writer lock 解放・HTTP エラーの safe_message 化) を取り込む。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore: bump version to 1.40.0 / notecli を v0.8.1 に更新
カラムクエリのキャッシュ遡り検索 (#783 Phase 3) はタイムライン種別で 絞っていなかった — notecli#30 以前は所属が後勝ち上書きで母集合を保証できず、 種別で絞ると取りこぼしになるため「混ざる側」に倒した妥協だった (068bb21 が notecli#30 待ちと明文化)。 実体/所属分離が入ったので、notecli の scan_cached_notes に追加された scope (notecli#57) を配線して妥協を解消する: - qir_search_cache に timeline_key (canonical, nullable) を追加。 parse 失敗は Err で顕在化、null は従来の全体走査 - フロントは loadMoreFromCache の searchable 分岐で columnCacheKey 由来の cacheKey を渡す — 別種別のノートが検索結果に混ざらなくなる - notecli rev を 203d9d8 (scan scope 追加) に bump Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uery feat(column-query): キャッシュ検索をカラムの所属バケットで絞る
API surface diff外部アプリ向け API 面 ( Full diffdiff --git a/src-tauri/openapi.json b/src-tauri/openapi.json
index 82883e5..b55f9c3 100644
--- a/src-tauri/openapi.json
+++ b/src-tauri/openapi.json
@@ -6,7 +6,7 @@
"license": {
"name": "MIT"
},
- "version": "1.39.0"
+ "version": "1.40.0"
},
"paths": {
"/api": {
@@ -1121,7 +1121,7 @@
{
"name": "tl_type",
"in": "path",
- "description": "Timeline type: home | local | social | global",
+ "description": "Timeline type: home | local | social | global (or fork-specific basic timelines like bubble)",
"required": true,
"schema": {
"type": "string"
@@ -1170,6 +1170,16 @@
}
}
},
+ "400": {
+ "description": "Invalid or non-basic timeline key",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/ApiErrorResponse"
+ }
+ }
+ }
+ },
"401": {
"description": "Unauthorized",
"content": {
diff --git a/src/bindings.ts b/src/bindings.ts
index 5a7639c..d697552 100644
--- a/src/bindings.ts
+++ b/src/bindings.ts
@@ -88,14 +88,20 @@ async qirValidate(query: QirQuery) : Promise<QirValidation> {
* 偽陰性を出さない規則に従うので (不変条件 (b))、FTS で落ちたノートが
* 本来マッチするということはない。
*
+ * `timeline_key` (canonical 文字列) を渡すと当該バケット所属のみを母集合に
+ * する。実体/所属分離 (notecli#30) 以前は所属が後勝ち上書きで種別絞りが
+ * 取りこぼしになるため全体走査しかなかったが、その妥協は解消済み。
+ * null は従来どおりアカウントの全キャッシュを走査する。
+ *
* 走査上限に達したら打ち切って継続カーソルを返す。呼び出し側は必要なだけ
- * 繰り返す (一度の呼び出しで巨大キャッシュを読み切らせない)。
+ * 繰り返す (一度の呼び出しで巨大キャッシュを読み切らせない)。カーソルは
+ * 同じ timeline_key の続き読みにのみ使うこと。
*
* @see src-tauri/src/commands/column_query.rs
*/
-async qirSearchCache(accountId: string, query: QirQuery, limit: number | null, maxScannedRows: number | null, cursor: QirSearchCursor | null) : Promise<Result<QirSearchResult, { code: string; message: string; apiCode: string | null }>> {
+async qirSearchCache(accountId: string, query: QirQuery, timelineKey: string | null, limit: number | null, maxScannedRows: number | null, cursor: QirSearchCursor | null) : Promise<Result<QirSearchResult, { code: string; message: string; apiCode: string | null }>> {
try {
- return { status: "ok", data: await TAURI_INVOKE("qir_search_cache", { accountId, query, limit, maxScannedRows, cursor }) };
+ return { status: "ok", data: await TAURI_INVOKE("qir_search_cache", { accountId, query, timelineKey, limit, maxScannedRows, cursor }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
@@ -277,7 +283,7 @@ async apiUpdateUserSetting(accountId: string, key: string, value: boolean) : Pro
}
},
/** @see src-tauri/src/commands/timeline.rs */
-async apiGetTimeline(accountId: string, timelineType: TimelineType, options: TimelineOptions | null) : Promise<Result<NormalizedNote[], { code: string; message: string; apiCode: string | null }>> {
+async apiGetTimeline(accountId: string, timelineType: string, options: TimelineOptions | null) : Promise<Result<NormalizedNote[], { code: string; message: string; apiCode: string | null }>> {
try {
return { status: "ok", data: await TAURI_INVOKE("api_get_timeline", { accountId, timelineType, options }) };
} catch (e) {
@@ -747,9 +753,25 @@ async apiGetCachedTimeline(accountId: string, timelineType: string, limit: numbe
}
},
/** @see src-tauri/src/commands/timeline.rs */
-async apiDeleteCachedNote(noteId: string) : Promise<Result<null, { code: string; message: string; apiCode: string | null }>> {
+async apiDeleteCachedNote(accountId: string, noteId: string) : Promise<Result<null, { code: string; message: string; apiCode: string | null }>> {
try {
- return { status: "ok", data: await TAURI_INVOKE("api_delete_cached_note", { noteId }) };
+ return { status: "ok", data: await TAURI_INVOKE("api_delete_cached_note", { accountId, noteId }) };
+} catch (e) {
+ if(e instanceof Error) throw e;
+ else return { status: "error", error: e as any };
+}
+},
+/**
+ * エンティティ削除 (antenna / clip / list — フロントは汎用 api_request で
+ * サーバー削除する) の後始末: 死にバケットの membership を破棄する。
+ * これが無いと削除済みエンティティの membership が sweep 対象外のまま
+ * per-account cap まで残留する (issue notecli#30 仕様 v5 §6-7)。
+ *
+ * @see src-tauri/src/commands/timeline.rs
+ */
+async apiClearTimelineCache(accountId: string, timelineKey: string) : Promise<Result<number, { code: string; message: string; apiCode: string | null }>> {
+ try {
+ return { status: "ok", data: await TAURI_INVOKE("api_clear_timeline_cache", { accountId, timelineKey }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
@@ -757,12 +779,10 @@ async apiDeleteCachedNote(noteId: string) : Promise<Result<null, { code: string;
},
/**
* Bulk-verify cached notes against the server.
- * Returns a map of note_id → fresh NormalizedNote for notes that still exist.
- * Missing notes (404) are omitted from the result.
*
* @see src-tauri/src/commands/timeline.rs
*/
-async apiVerifyNotes(accountId: string, noteIds: string[]) : Promise<Result<Partial<{ [key in string]: NormalizedNote }>, { code: string; message: string; apiCode: string | null }>> {
+async apiVerifyNotes(accountId: string, noteIds: string[]) : Promise<Result<VerifyNotesResult, { code: string; message: string; apiCode: string | null }>> {
try {
return { status: "ok", data: await TAURI_INVOKE("api_verify_notes", { accountId, noteIds }) };
} catch (e) {
@@ -771,9 +791,9 @@ async apiVerifyNotes(accountId: string, noteIds: string[]) : Promise<Result<Part
}
},
/** @see src-tauri/src/commands/timeline.rs */
-async apiGetCachedTimelineBefore(accountId: string, timelineType: string, before: string, limit: number | null) : Promise<Result<NormalizedNote[], { code: string; message: string; apiCode: string | null }>> {
+async apiGetCachedTimelineBefore(accountId: string, timelineType: string, before: string, beforeNoteId: string | null, limit: number | null) : Promise<Result<NormalizedNote[], { code: string; message: string; apiCode: string | null }>> {
try {
- return { status: "ok", data: await TAURI_INVOKE("api_get_cached_timeline_before", { accountId, timelineType, before, limit }) };
+ return { status: "ok", data: await TAURI_INVOKE("api_get_cached_timeline_before", { accountId, timelineType, before, beforeNoteId, limit }) };
} catch (e) {
if(e instanceof Error) throw e;
else return { status: "error", error: e as any };
@@ -2004,6 +2024,9 @@ async exportDb() : Promise<Result<boolean, { code: string; message: string; apiC
* Import notecli.db from a user-chosen file via open dialog.
* Replaces the current database file. Caller should relaunch the app afterwards
* so that Rust re-opens the new DB with a fresh connection.
+ *
+ * 注意: V6 (note_timelines) 適用済みの DB は旧バージョンのアプリへ持ち込めない
+ * (refinery の missing migration で open 不能。DB 自体は無傷)。
*
* @see src-tauri/src/commands/utility.rs
*/
@@ -2638,7 +2661,7 @@ async aiMigrateProviderToVault(provider: string, name: string, baseUrl: string,
}
},
/** @see src-tauri/src/query_runtime.rs */
-async querySubscribeTimeline(accountId: string, timelineType: TimelineType, listId: string | null) : Promise<Result<QuerySnapshot, { code: string; message: string; apiCode: string | null }>> {
+async querySubscribeTimeline(accountId: string, timelineType: string, listId: string | null) : Promise<Result<QuerySnapshot, { code: string; message: string; apiCode: string | null }>> {
try {
return { status: "ok", data: await TAURI_INVOKE("query_subscribe_timeline", { accountId, timelineType, listId }) };
} catch (e) {
@@ -2710,15 +2733,6 @@ async querySubscribeChatRoom(accountId: string, roomId: string) : Promise<Result
}
},
/** @see src-tauri/src/query_runtime.rs */
-async queryOpen(key: QueryKey) : Promise<Result<QuerySnapshot, { code: string; message: string; apiCode: string | null }>> {
- try {
- return { status: "ok", data: await TAURI_INVOKE("query_open", { key }) };
-} catch (e) {
- if(e instanceof Error) throw e;
- else return { status: "error", error: e as any };
-}
-},
-/** @see src-tauri/src/query_runtime.rs */
async querySetRuntimeState(queryId: string, state: QueryRuntimeState) : Promise<Result<QuerySnapshot, { code: string; message: string; apiCode: string | null }>> {
try {
return { status: "ok", data: await TAURI_INVOKE("query_set_runtime_state", { queryId, state }) };
@@ -3121,13 +3135,18 @@ export type EmojiChangeKind = "added" | "updated" | "deleted"
*/
export type EvictionConfig = {
/**
- * 各アカウントごとの note 上限。`None` なら無制限。
+ * 各アカウントごとの note (entity) 上限。`None` なら無制限。
*/
perAccountLimit: number | null;
/**
* `cached_at` の TTL (日)。`None` なら無期限保持。
*/
-ttlDays: number | null }
+ttlDays: number | null;
+/**
+ * バケット (account_id × timeline_key) ごとの所属行上限。`None` なら無制限。
+ * トリムは membership とその対象限定の orphan entity のみを消す。
+ */
+perTimelineLimit: number | null }
/**
* EXIF 1 フィールド。tag はタグ名 (例: "DateTimeOriginal", "GPSLatitude")。
*/
@@ -3534,7 +3553,14 @@ updates: NoteUpdate[] }
* the frontend receives a discriminated union; every variant carries `id`.
*/
export type QueryItem = ({ kind: "note" } & NormalizedNote) | ({ kind: "notification" } & NormalizedNotification) | ({ kind: "chatMessage" } & ChatMessage)
-export type QueryKey = { kind: "timeline"; account_id: string; timeline_type: TimelineType; list_id: string | null } | { kind: "antenna"; account_id: string; antenna_id: string } | { kind: "channel"; account_id: string; channel_id: string } | { kind: "role"; account_id: string; role_id: string } | { kind: "mentions"; account_id: string } | { kind: "notifications"; account_id: string } | { kind: "chatUser"; account_id: string; other_id: string } | { kind: "chatRoom"; account_id: string; room_id: string }
+export type QueryKey =
+/**
+ * notes 系購読の統一キー。`key` は `TimelineKey` の canonical 文字列
+ * (home / antenna:{id} / channel:{id} / role:{id} / user-list:{id} / mentions)。
+ * 折り畳まないと同一購読が 2 通りのキー表現を持ち dedup が破れる。
+ * mentions は識別子としてのみここに属し、購読自体は main チャンネル経由。
+ */
+{ kind: "timeline"; account_id: string; key: string } | { kind: "notifications"; account_id: string } | { kind: "chatUser"; account_id: string; other_id: string } | { kind: "chatRoom"; account_id: string; room_id: string }
export type QueryReadModelSnapshot = { queryId: string; revision: number;
/**
* Note ids in display order (newest first). 消費側は JS noteStore から
@@ -3657,7 +3683,6 @@ export type StreamStatusEvent = { accountId: string; state: StreamConnectionStat
export type SummaryData = { title: string | null; description: string | null; icon: string | null; sitename: string | null; thumbnail: string | null; medias: string[]; player: Player | null; url: string; sensitive: boolean }
export type TimelineFilter = { withRenotes: boolean | null; withReplies: boolean | null; withFiles: boolean | null; withBots: boolean | null; withSensitive: boolean | null }
export type TimelineOptions = { limit?: number; sinceId: string | null; untilId: string | null; filters?: TimelineFilter | null; listId: string | null }
-export type TimelineType = string
/**
* 「確認なしで使う」のプラグイン個体単位の記憶。
*
@@ -3850,6 +3875,21 @@ ok: boolean;
* 失敗時の理由 (SSRF / timeout / DNS など)。secret は含まない。
*/
error: string | null }
+/**
+ * `api_verify_notes` の結果。
+ */
+export type VerifyNotesResult = {
+/**
+ * サーバー上に現存が確認できたノート (note_id → 最新の NormalizedNote)
+ */
+verified: Partial<{ [key in string]: NormalizedNote }>;
+/**
+ * サーバーが NO_SUCH_NOTE を返した = 削除が確認できたノート id。
+ * 通信エラー・レート制限・タイムアウトはどちらにも含まれない (生存扱い) —
+ * 復帰直後の不安定なネットワークでキャッシュを誤って恒久削除しないため
+ * (issue notecli#30 仕様 v5 §6-8)。
+ */
+missing: string[] }
/** tauri-specta globals **/
|
📝 WalkthroughWalkthroughNotedeck 1.40.0 introduces canonical timeline keys across Tauri commands, query subscriptions, cache storage, and deck columns. Cache operations gain account and timeline scoping, stable pagination cursors, structured verification results, safe-mode query bypasses, and updated query evaluation. ChangesTimeline cache and query migration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant DeckColumn
participant useNoteColumn
participant TauriCacheAPI
participant CacheDatabase
DeckColumn->>useNoteColumn: request notes using column cache key
useNoteColumn->>TauriCacheAPI: search cached notes with timeline scope
TauriCacheAPI->>CacheDatabase: scan scoped cache bucket
CacheDatabase-->>useNoteColumn: cached notes and cursor
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@src-tauri/src/commands/timeline.rs`:
- Around line 162-176: Await the blocking cache invalidation task before
reporting success, while retaining warning-only handling when cleanup fails. In
src-tauri/src/commands/timeline.rs lines 162-176, update the antenna mutation
flow around update_antenna and clear_timeline; in src-tauri/src/commands/user.rs
lines 465-478, apply the same change to the user-list invalidation flow before
returning Ok(()). Add a race test that refreshes while invalidation is pending.
🪄 Autofix (Beta)
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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c58fe863-c551-49fb-8c6e-fae2ee054c3c
⛔ Files ignored due to path filters (1)
src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (42)
package.jsonsrc-tauri/Cargo.tomlsrc-tauri/openapi.jsonsrc-tauri/src/commands/admin.rssrc-tauri/src/commands/column_query.rssrc-tauri/src/commands/timeline.rssrc-tauri/src/commands/user.rssrc-tauri/src/commands/utility.rssrc-tauri/src/lib.rssrc-tauri/src/query_runtime.rssrc-tauri/src/streaming.rssrc-tauri/tauri.conf.jsonsrc/aiscript/events.test.tssrc/bindings.tssrc/commands/cliHandlers.tssrc/components/common/NoteMoreMenu.vuesrc/components/deck/DeckAntennaColumn.vuesrc/components/deck/DeckChannelColumn.vuesrc/components/deck/DeckClipColumn.vuesrc/components/deck/DeckColumnsArea.vuesrc/components/deck/DeckExploreColumn.vuesrc/components/deck/DeckFavoritesColumn.vuesrc/components/deck/DeckListColumn.vuesrc/components/deck/DeckMentionsColumn.vuesrc/components/deck/DeckNotificationColumn.vuesrc/components/deck/DeckRoleColumn.vuesrc/components/deck/DeckTimelineColumn.vuesrc/components/deck/DeckUserColumn.vuesrc/components/window/NoteDetailContent.vuesrc/composables/useColumnSetup.tssrc/composables/useCrossAccountNotes.tssrc/composables/useEntityCrud.tssrc/composables/useNoteColumn.dom.test.tssrc/composables/useNoteColumn.tssrc/composables/useNoteColumnCache.test.tssrc/composables/useNoteColumnCache.tssrc/composables/useNoteList.tssrc/core/queryRegistry.test.tssrc/core/queryRegistry.tssrc/utils/cacheEviction.tssrc/utils/columnCacheKey.test.tssrc/utils/columnCacheKey.ts
api_update_antenna / api_remove_user_from_list がバケット破棄を detach していたため、コマンド成功を受けたフロントの再フェッチが ingest した後に破棄が走り、再構築したばかりのキャッシュを消す レースがあった (#962 レビュー指摘)。spawn_blocking のまま await に変更 (warn + Ok の失敗方針と tokio worker 退避は維持)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: clear_timeline の完了を待ってからコマンドを返す
Deploying notedeck with
|
| Latest commit: |
6e1c277
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://04e10796.notedeck-d3a.pages.dev |
| Branch Preview URL: | https://develop.notedeck-d3a.pages.dev |
#783 の 2026-08-04 レビューコメントで確定した欠陥を修正する。 1. 🐢 降格経路で名前付きクエリ形 `@(note) { ... }` が fail-closed になる: createReferenceFilter が無条件ラップしていたため、fn 式のクエリは本体が fn 値になり全ノート error だった。⚡ コンパイラと同じ「単一 1 引数 fn 式」 判定を入れてラップを切り替える (引数名は execFnSync の位置渡しで不問) 2. nullability の 3 面不一致: NOTE_FIELD_TYPES の files / reactions / localOnly から NULL を除去 (Rust serde 形は常に存在)。死に枝の解消と V25 偽警告 (files への不要ガード提案) の停止。localOnly は TS 型も必須化 し JS 面 (undefined→null) と Rust 面 (常に bool) の評価不一致を封じる。 null リテラル比較の許可規則により配布クエリ (MisStore text-only の `note.files == null`) は引き続きコンパイル可 (テストで固定)。serde 形に 忠実な golden vector を追加 3. セーフモード連携 (#838 条件 3 / 仕様項目 10): readSafeMode() 時は compiledQuery を null にし、コンパイル・Worker 起動・QIR キャッシュ検索の すべてを最上流で止める (フィルタなし表示 = fail-open、プラグインと同じ 「自動実行されるユーザーコードを止める」意味論) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(column-query): レビュー指摘 3 件の修正 (#783)
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/composables/useNoteColumn.ts (1)
1000-1005: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAdvance
fetchCursorwith compound ordering.Compare
(createdAt, id)inoldestOfandadvanceFetchCursorusing the backend keyset order. Timestamp-only comparisons retain the first equal-timestamp note and can repeat or skip notes during legacy and QIR cache pagination. Add duplicate-createdAttests for both paths.🤖 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 `@src/composables/useNoteColumn.ts` around lines 1000 - 1005, Update the cursor advancement logic in useNoteColumn.ts around oldestOf and advanceFetchCursor to compare the compound backend keyset order (createdAt, id), not timestamps alone. Ensure equal-createdAt notes advance according to id so legacy and QIR cache pagination neither repeats nor skips notes, and add duplicate-createdAt coverage for both paths.
🤖 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.
Outside diff comments:
In `@src/composables/useNoteColumn.ts`:
- Around line 1000-1005: Update the cursor advancement logic in useNoteColumn.ts
around oldestOf and advanceFetchCursor to compare the compound backend keyset
order (createdAt, id), not timestamps alone. Ensure equal-createdAt notes
advance according to id so legacy and QIR cache pagination neither repeats nor
skips notes, and add duplicate-createdAt coverage for both paths.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6689bc5f-6b03-4f4e-a6f2-41c9c7f7fdbd
⛔ Files ignored due to path filters (1)
src/services/columnQuery/golden/qir.generated.jsonis excluded by!**/*.generated.*
📒 Files selected for processing (15)
src/adapters/types.tssrc/components/deck/DeckQueryManagerColumn.vuesrc/composables/useCrossAccountNoteActions.test.tssrc/composables/useNoteColumn.dom.test.tssrc/composables/useNoteColumn.tssrc/engine/threadMerge.test.tssrc/services/columnQuery/compiler.test.tssrc/services/columnQuery/compiler.tssrc/services/columnQuery/golden/vectors.jsonsrc/services/columnQuery/referenceEvaluator.test.tssrc/services/columnQuery/referenceEvaluator.tssrc/services/entityResolution.test.tssrc/utils/noteViewModel.test.tssrc/utils/replyMentions.test.tssrc/utils/safeMode.ts
closes #964, closes #965 #965: 複数 ⚡ パーツの QIR を And 合成する composeQir を新設。let の スロット番号はパーツごと 0 起点のため、「前パーツまでの最大スロット+1」で renumber してから左結合の二項 And で束ねる (「slot は式全体で一意」という QIR 契約の維持)。cacheSearchableQuery の fast.length === 1 制約を撤廃し、 名前付きクエリ 2 個トグル等でキャッシュ検索が黙って無効になる問題を解消。 #964: オンラインの loadMore で「API ページは取得できたがフィルタ後の獲得が 0」のとき、同一呼び出し内でキャッシュ検索を 1 パス連結する (issue 案 (b) の N=1)。API ページは api_get_timeline で ingest 済みで、キャッシュ検索は fetchCursor より古い側のみ走査するため二重読みにならない。マッチ率が低い クエリでもオンラインの「もっと読む」1 回でカラムが埋まる。キャッシュ検索 1 パスは runCacheSearchPass に抽出し offline 経路 (loadMoreFromCache) と共用。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(column-query): And 合成とオンライン時のキャッシュ検索チェーン
- §1.2 を新設: cheapCheckCapabilities の意味論 (宣言 skill のみ発動、 未宣言だと毎 tick AI 呼び出し、time.now 等はスキップが効かない) - §10 Known Limits に MisStore インストール時の mode 正規化 (heartbeat → manual) と isPersona 未取込 (#967) を追記 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
変更一覧(v1.39.0 → v1.40.0)
ユーザー可視の変化
userListチャンネル名修正)。vmimi-relay 等フォーク独自 TL の WS 購読も修正@(note) { ... }形が 🐢 降格でも動く / セーフモードでクエリフィルタが停止更新時の注意(リリースノート転記用)
🤖 Generated with Claude Code