Skip to content

Release v1.48.0 - #1054

Merged
hitalin merged 16 commits into
mainfrom
develop
Aug 12, 2026
Merged

hitalin merged 16 commits into
mainfrom
develop

Conversation

@hitalin

@hitalin hitalin commented Aug 12, 2026 •

Copy link
Copy Markdown
Collaborator

概要

AI の編集を承認する前に差分で見せる (#981) を中心としたリリース。確認ダイアログが編集後の断片しか出しておらず、承認の判断材料が AI の自己申告だけになっていた状態を解消する。あわせて編集履歴を人が読める導線を用意し、コード面の配色をアプリのテーマに追従させる。

Closes #981

主な変更

適用前の差分表示 (#981)

  • skill / plugin / widget / theme / カスタム CSS の write 確認に、編集前と適用後の全文 diff を表示する
  • 部分編集 (追記・セクション置換・props patch) も適用後の全文を確認時点で計算する。断片 diff は文脈が消えるため採用しない
  • 承認後は再計算せず、確認に使った全文をそのまま書き込む。確認と書込の間に元が変わっていたら書かずに中止する

編集履歴

  • 各エディタの「履歴」から開く一時ウィンドウを追加。選んだスナップショットを「その編集で何が変わったか」の差分で読め、戻す操作も同居する (開発者モード限定)
  • 内容が変わらない保存では履歴を積まない。自動保存でリングが埋まり、意味のある編集前の状態が押し出されるのを防ぐ
  • 履歴からの巻き戻しが、開いたままのエディタに反映されるようになった (未保存の編集がある場合はユーザーの編集を優先する)

再インストールの保護

  • skill / widget / theme / クエリの再インストールが、ローカル編集を無言で上書きしていた。中身が変わる場合は差分付きの確認を通す (プラグインは対応済みだった)

表示

  • AI チャットのメッセージ本文のコードブロックにシンタックスハイライトを適用。diff は増減が読み取れる色で行ごとに塗る
  • コード面 (エディタ / 差分表示 / コードブロック) の明暗をアプリのテーマに追従させる。ライトテーマでは明るい面と明るいトークン色になる

変更一覧

確認事項

  • テスト 3035 passed / typecheck / lint / docs-lint すべて通過
  • 実機確認は WSL2 で軽く実施。ライトテーマでのコード面の見え方 (エディタのガター・カーソル・補完ポップアップ、ノート本文のコードブロック、AI チャットのツール入出力) は Windows 側での確認が残っている
  • 既定の見た目が変わる点: ライトテーマ利用時、コード面が明るくなる

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added edit history for skills, widgets, plugins, themes, and custom CSS, including diff previews and revert support.
    • Added confirmation previews for content updates and reinstalls.
    • Added light and dark code-editor themes with theme-aware syntax highlighting.
    • Editors now synchronize external changes while preserving unsaved local edits.
  • Bug Fixes

    • Prevented stale confirmations from overwriting newer edits.
    • Avoided creating history entries when content is unchanged.

hitalin and others added 16 commits August 12, 2026 13:17
skill / plugin / widget / theme / カスタム CSS の write 確認が編集後の断片
しか出しておらず、承認の判断材料が AI の自己申告だけになっていた。#1040 で
入った CodeDiffView と ConfirmOptions.diff を各 capability へ配線し、
編集前と適用後の全文を並べて見せる。

- 部分編集 (追記・セクション置換・props patch) も適用後の全文を確認時点で
  計算して全文 diff で見せる。断片 diff は文脈が消えるため採用しない
- 適用後全文の計算は services 層の純関数へ (skills.ts の
  replaceMarkdownSection もここへ移設)
- 承認後は再計算せず、確認に使った全文をそのまま書き込む。確認と書込の間に
  元が変わっていたら書かずに中止する (capabilities/stagedEdit.ts)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
編集履歴は capability (`*.history`) からしか読めず、AI が過去に何を変えたか
を人間が後から追う手段が無かった。各エディタの「履歴」から開く一時ウィンドウ
を追加し、確認ダイアログと同じ CodeDiffView で読めるようにする。

- 選んだ snapshot は「その編集の直前の状態」なので、比較相手は 1 つ新しい
  snapshot (無ければ現在の内容)。snapshot 間と snapshot vs 現在が同じ
  見え方に収まる
- 戻す操作は store 直叩きではなく revert capability を dispatcher 経由で
  呼ぶ (確認ダイアログと「見せたものを書く」不変条件をそのまま通す)
- 種別ごとの差分 (snapshot → 全文テキスト・言語・revert 先) は
  services/editHistory.ts に集約し、ウィンドウ側は kind で分岐しない
- 導線はスキル / ウィジェット / プラグイン / テーマ / カスタム CSS の 5 エディタ

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CSS エディタは開いた瞬間の内容をローカルバッファに持つだけで、store から
編集バッファへ戻す経路が無かった。revert 自体は効いている (適用もファイル
書込も走る) が、エディタの表示は古いままで、タブ切替やプリセット操作で
古い内容が書き戻され、巻き戻しが無言で打ち消される。

- custom.css の外部変更 (履歴からの revert / AI 編集 / 外部エディタ) を
  編集バッファへ取り込む watch を追加。取り込み中は書込み側の watch を止め、
  待機中のデバウンスも捨てる (再構築した内容を保存し返さないため)
- 履歴ウィンドウの「現在の内容」を prop の静的コピーから store 参照に変更。
  revert 後も比較相手が実態を指す (種別ごとの取り出しは composable に集約)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
skill / widget / theme の `*.install` は既存インストールがあると上書き更新に
なるが、確認はストアの説明文を出すだけで本体の diff を通らなかった。AI 経由の
再インストールでユーザーのローカル編集が無言で消える。プラグインだけは
confirmPluginUpdate 経由で塞がっていた (#1040 の権限再同意の副産物) ので、
残り 3 種別を同じ形に揃える。

- confirmSkillUpdate / confirmWidgetUpdate / confirmThemeUpdate を
  install の既存分岐と update* の共用点として切り出す
- install 経路は「中身が実際に変わるとき」だけ確認する。テーマは表示に効く
  部分 (name / base / props) で判定し、別アカウントへの installedFor 追加
  だけの再インストールでは確認を出さない
- 確認を断ったら本体もストア由来メタも書き換えない (更新バッジは残る)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
確認ダイアログや tool 入出力は Shiki を通っているのに、AI メッセージ本文の
markdown だけ素の pre/code で出していた。フェンスの言語は data-lang に
記録するだけでハイライタを呼んでいなかった。

- highlight.ts に highlightCodeTokens を追加 (pre/code を持たず span 列だけ
  返す)。独自の pre 構造 — コピーボタン同居 — を壊さずにハイライトできる
- ハイライトできた塊は背景も shiki 側に譲る。dark-plus のトークン色前提なので
  ライトテーマの明るい背景だと潰れる
- AI が編集内容を ```diff で見せることがあるので diff 文法を遅延ロードに追加
- ハイライタは非同期ロードなので、メッセージ本文も highlighterLoaded を key に
  持たせて読み込み完了後に再描画する

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- ```diff は Shiki に渡さず行単位で塗る。dark-plus の diff は淡い前景色 1 色で、
  吹き出しの中では増減が読み取れない。確認ダイアログの diff (CodeDiffView) と
  同じグローバル変数 (--nd-diffInsertBg / --nd-diffDeleteBg) を使って色の意味を
  揃える。行は block の span 1 つに閉じ、pre の改行文字は挟まない (二重にかかると
  1 行おきに空行が出る)
- highlightRevision を追加し、描画側の再描画キーをこれに載せ替える。
  highlighterLoaded (boolean) は初期化でしか変わらないので、遅延ロードの言語
  (python / diff 等) は「ロードが終わっても誰も再描画しない」ままだった
- コードブロックのコピーボタンをテキストラベルからアイコンに変更し、メッセージ
  単位のコピーボタン (ti-copy → ti-check) と見た目を揃える

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CSS エディタと同じ構造が残りの 3 エディタにもあった。開いた瞬間の内容を
ローカルバッファに持つだけで store から戻す経路が無く、履歴ウィンドウから
revert しても表示は古いまま、次の操作で古い内容が書き戻される。

- ウィジェット / プラグイン / テーマの各エディタに store からの再同期を追加
- 未保存の編集があるときはユーザーのバッファを優先する (revert より本人の
  編集が新しい。保存すれば本人の内容で上書きされる = 明示操作)
- 自分の書込みで再同期が走らないよう、値の一致とテーマ内容のキーで弾く

スキルエディタは store が要素ごと差し替えるため既に追従していた。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
skill / widget / theme を揃えた前コミットでカラムクエリを取りこぼしていた。
updateQuery には diff 確認があるのに installQuery の既存分岐が無確認で
applyStoreUpdate を呼んでおり、同じ非対称が残っていた。

confirmQueryUpdate を install の既存分岐と updateQuery の共用点として切り出す
(confirmSkillUpdate / confirmWidgetUpdate / confirmThemeUpdate と同型)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- 履歴一覧を横スクロールのチップから縦リストへ。件数が増えたとき横だと
  見えない項目が出るうえ、時系列の向き (上が新しい) が読み取れない。
  最新の 1 件に「直前」タグを付けて現在との関係を明示する
- 編集履歴ウィンドウに developer の帰属タグを付け、各エディタの「履歴」
  ボタンも開発者モードが無効なら出さない (#1034 — 隠すのは入口だけ)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
スキル / ウィジェット / プラグインの保存は、内容が変わったかを見ずに編集前
snapshot を積んでいた (同値ガードがあるのは custom.css だけ)。これらの
エディタはデバウンスの自動保存なので、意味のある区切りと無関係に積まれ、
10 件のリングが短時間で埋まる。「AI が編集する前の状態」が押し出されると
履歴の目的そのものが果たせない。

スキルは snapshot が記録する範囲 (本文 / 名前 / バージョン / モード) が
動いたときだけ積む。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4 エディタがそれぞれ別実装で外部変更を取り込んでおり、壊れるとユーザーの
編集が消える方向のバグになるのに、手作業で確かめるのが難しかった
(未保存の編集を守れているかは目視では分からない)。

useExternalEditSync に規則を 1 本化し、ユニットテストで固定する:

- バッファと同じ値なら何もしない (自分の書込みの再取り込みを防ぐ)
- 未保存の編集があるときは取り込まない (revert より本人の編集が新しい)
- 取り込み中は isSyncing() が true。書込み側の watch をここで止めないと、
  取り込んだ内容を保存し返す (エディタによっては再構築した内容に化ける)

バッファの形も dirty 判定もエディタごとに違うので、判定は呼び出し側から
受け取る。テストは effectScope で回すのでコンポーネントの mount は不要。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
規則は useExternalEditSync のユニットテストで固定したが、どのエディタが何を
「未保存の編集」として渡しているかはそこでは分からない。取り違えても型は
通り、壊れるとユーザーの編集中バッファが無言で消える。

@vue/test-utils を dev 依存に追加し、4 エディタを shallow mount して
「未編集なら外部変更を取り込む」「編集中なら打ちかけを消さない」を検証する。
カスタム CSS は未保存という状態を持たない代わりに、取り込んだ内容が
再構築された内容に化けないこと (書込み側の watch を止められていること) を見る。

各テストが実際に配線の壊れを捕まえることは、isDirty を潰す / 抑止を外す
改変で落ちることを確認済み。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
シンタックスハイライトのトークン色が VS Code Dark+ 準拠でダーク固定なのに、
面の色だけテーマ追従だったり未定義トークンのフォールバックだったりで
バラバラだった。ライトテーマでは淡いトークン色が明るい面の上に乗り、
特に diff の増減が読めなかった。

- 面の色を --nd-codeEditorBg / 新設の --nd-codeEditorFg に集約する
- 対象: ノート本文の MFM コードブロック / 確認ダイアログ / AI チャットの
  メッセージ本文とツール入出力 / 診断ログ
- shiki-dark-plus.css は色を直書きするので、要素を足した特異度で
  グローバル側を勝たせ、面の色をトークン 1 箇所から決まるようにする
- AI チャットのメッセージ本文では、インライン code の装飾がブロック内の
  code にも効いていたのを打ち消す (面の中にもう一枚面が乗っていた)

AiScript カラムの値インスペクタはハイライト対象ではない (ダークのトークン色
が乗らない) ため、従来どおりテーマ追従のままにする。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
エディタ / 差分表示 / コードブロックはトークン色が暗い面を前提にした一式しか
なく、面をダーク固定にするしかなかった。明るい面用のトークン色を用意し、
アピアランス設定から明暗を選べるようにする。

- 設定は 1 つ (appearance.codeScheme: dark / light / auto)。編集と表示で
  分ける理由が無いので項目を増やさない。既定は auto (アプリのテーマに追従)
- 実効値は root の data-nd-code-scheme に出し、CSS 変数がそれを見る。
  CodeMirror のテーマは色の直書きをやめて変数から取るので、設定を変えても
  エディタを作り直さずに追従する
- Shiki はテーマごとに色クラスが変わるため light-plus のパレットを追加し、
  切替時は既存の再描画機構 (highlightRevision) で描き直す
- #1050 の一本化はしない。明暗対応に必要なのは light パレットだけで、
  本家追従の例外も言語カバレッジの退行も伴わない

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
設定項目を増やさず、触れば分かる挙動で完結させる方針に合わせる。アプリが
ダークならコード面もダーク、ライトならライト、アプリが OS 追従ならコード面も
OS に追従する。明暗を別扱いしたい場合はカスタム CSS で変数を上書きできる。

明るい面用のトークン色・変数化・Shiki のパレット切替はそのまま残す
(設定の有無に関係なく、ライトテーマで面だけ明るくすると読めなくなるため)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added 📖Doc Documentation related issue/PR rust Pull requests that update rust code javascript Pull requests that update javascript code labels Aug 12, 2026
@github-actions

Copy link
Copy Markdown

API surface diff

外部アプリ向け API 面 (src-tauri/openapi.json / src/bindings.ts) が変更されています。
互換性への影響 (#709) をレビューしてください。

src-tauri/openapi.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Full diff
diff --git a/src-tauri/openapi.json b/src-tauri/openapi.json
index 79287a7..2e236d6 100644
--- a/src-tauri/openapi.json
+++ b/src-tauri/openapi.json
@@ -6,7 +6,7 @@
     "license": {
       "name": "MIT"
     },
-    "version": "1.47.0"
+    "version": "1.48.0"
   },
   "paths": {
     "/api": {

@hitalin hitalin self-assigned this Aug 12, 2026
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​vue/​test-utils@​2.4.11991009388100

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm js-beautify is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yaml → npm/@vue/test-utils@2.4.11 → npm/js-beautify@1.15.4

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/js-beautify@1.15.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying notedeck with  Cloudflare Pages  Cloudflare Pages

Latest commit: b8d6db4
Status: ✅  Deploy successful!
Preview URL: https://5aebd8d6.notedeck-d3a.pages.dev
Branch Preview URL: https://develop.notedeck-d3a.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds edit-history viewing and reversion for five content types. It stages confirmed full-file edits and rejects stale writes. It synchronizes external editor changes. It adds reactive light/dark code rendering and updates the application version to 1.48.0.

Changes

Edit history and staged edits

Layer / File(s) Summary
Staged confirmation edits
src/capabilities/*, src/services/selfEditApply.*
Capabilities now show full diffs, preserve approved content through execution, and reject writes when the source changed after confirmation.
Reinstall confirmation and snapshot deduplication
src/stores/misstore.*, src/stores/{plugins,skills,widgets}.*
Reinstall flows use confirmation handlers. History snapshots are skipped for unchanged content.
History window and editor synchronization
src/components/window/*, src/services/editHistory.*, src/composables/*, src/stores/windows.ts, src/windows/*
The new history window loads snapshot diffs and dispatches reverts. Skill, widget, plugin, theme, and CSS editors expose history actions and accept clean external updates without overwriting dirty buffers.

Code rendering

Layer / File(s) Summary
Themed code rendering and editor surfaces
src/utils/highlight.ts, src/composables/useCodeScheme.ts, src/styles/global.css, src/aiscript/codemirror/theme.ts, src/components/common/*, src/components/deck/DeckAiColumn.vue, src/components/window/AboutContent.vue, src/assets/shiki-light-plus.css, src/main.ts, src/App.vue
CodeMirror, Shiki, and code blocks use shared light/dark variables. Highlighting refreshes when themes or languages become available.

Release metadata

Layer / File(s) Summary
Release metadata and documentation
package.json, src-tauri/Cargo.toml, src-tauri/openapi.json, src-tauri/tauri.conf.json, DEVELOPMENT.md
Package metadata is updated to 1.48.0. Development documentation describes edit history, staged edits, and code schemes.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor as EditableView
  participant History as EditHistoryContent
  participant Service as EditHistoryService
  participant Dispatcher as CapabilityDispatcher
  participant Store as ContentStore
  Editor->>History: open edit history
  History->>Service: load snapshots and current text
  Service-->>History: return paired diff
  History->>Dispatcher: request revert
  Dispatcher->>Store: apply staged content
  Store-->>Editor: update persisted content
Loading

Possibly related PRs

  • notedeck-dev/notedeck#1047: This PR extends the same capability handlers and file-history infrastructure with staged diffs and history UI.
  • notedeck-dev/notedeck#830: Both PRs update application versions across package, Cargo, OpenAPI, and Tauri metadata.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title identifies the v1.48.0 release and matches the version changes, although it does not describe the release's main feature work.
Description check ✅ Passed The description clearly covers the changes, reasons, tests, and remaining verification work, although it uses different section headings than the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (1)
src/stores/misstore.ts (1)

1202-1206: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move themeBodyKey to src/services/ and make the key order-independent.

Two points:

  1. themeBodyKey is pure comparison logic inside the store setup. The repository guideline requires pure logic such as merge rules and codecs to live in src/services/ and to be unit tested directly, and limits stores to subscription, cache, and UI state.
  2. JSON.stringify of t.props depends on key insertion order. Two theme objects with the same properties in a different order produce different keys. The reinstall path then shows a confirmation dialog for a theme that did not change visually.

Sort the props keys before serialization.

♻️ Proposed change (service function)
// src/services/themeIdentity.ts
export function themeBodyKey(theme: Record<string, unknown>): string {
  const props = (theme.props ?? {}) as Record<string, unknown>
  const sorted = Object.keys(props)
    .sort()
    .map((k) => [k, props[k]] as const)
  return JSON.stringify([theme.name, theme.base, sorted])
}

Based on the coding guideline "正規化、マイグレーション、マージ規則、ファイル codec などの純ロジックは store に置かず src/services/ に実装し、直接ユニットテストする。store は購読、キャッシュ、UI 状態に限定する".

🤖 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/stores/misstore.ts` around lines 1202 - 1206, Move the pure themeBodyKey
comparison logic out of the store into a new src/services/themeIdentity.ts
service and update callers to import it. Make serialization independent of props
insertion order by sorting props keys and serializing the ordered entries, while
preserving the name/base values; add direct unit tests for equivalent themes
with differently ordered props.

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 `@DEVELOPMENT.md`:
- Line 721: Remove the hard-coded “10件” snapshot count from the root document’s
description and refer readers to src/utils/historyFs.ts as the source of truth.
If retaining the count is required, add docs-lint-disable-next-line with a clear
reason.

In `@src/capabilities/builtins/plugins.ts`:
- Around line 544-550: After store.updateSrc(installId, next) in the
plugins.revert flow, invoke the existing plugins.update relaunch behavior for
the affected plugin so an active plugin restarts with the reverted source;
preserve the current revert and persistence logic for inactive plugins.

In `@src/components/window/EditHistoryContent.vue`:
- Around line 52-57: Update reload and revert to handle rejected listSnapshots
and dispatchCapability calls: wrap each operation in try/finally so loading and
reverting are always reset, and record failures in the existing load-error state
or revertError. Move revertError above reload if reload uses it, while
preserving successful snapshot selection and revert behavior.

In `@src/stores/misstore.ts`:
- Around line 985-1021: Update updateWidget to report success only when
confirmWidgetUpdate returns a value that is neither false nor undefined; replace
the applied !== false condition while preserving cancellation and successful
update behavior.

In `@src/stores/widgets.ts`:
- Around line 371-373: Update applyStoreUpdate to call pushSnapshot only when
patch.src differs from the current widget.src, matching the guard in updateSrc
and preventing unchanged alwaysConfirm reinstalls from adding history entries.
Add a test covering confirmWidgetUpdate/applyStoreUpdate with an unchanged
source and verify no duplicate snapshot is recorded.

In `@src/styles/global.css`:
- Around line 124-128: Remove the duplicate --nd-codeString and --nd-codeNumber
declarations from the shown dark-scheme block in global.css, retaining a single
dark-scheme definition for each and preserving the later light-scheme overrides.
Leave the --nd-codeKeyword declaration unchanged unless it is also duplicated.

---

Nitpick comments:
In `@src/stores/misstore.ts`:
- Around line 1202-1206: Move the pure themeBodyKey comparison logic out of the
store into a new src/services/themeIdentity.ts service and update callers to
import it. Make serialization independent of props insertion order by sorting
props keys and serializing the ordered entries, while preserving the name/base
values; add direct unit tests for equivalent themes with differently ordered
props.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aac4d0f3-f8d2-458f-8dd9-0d44005530fa

📥 Commits

Reviewing files that changed from the base of the PR and between e064db5 and b8d6db4.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (60)
  • DEVELOPMENT.md
  • package.json
  • src-tauri/Cargo.toml
  • src-tauri/openapi.json
  • src-tauri/tauri.conf.json
  • src/App.vue
  • src/aiscript/codemirror/theme.ts
  • src/assets/shiki-light-plus.css
  • src/capabilities/builtins/plugins.test.ts
  • src/capabilities/builtins/plugins.ts
  • src/capabilities/builtins/skills.test.ts
  • src/capabilities/builtins/skills.ts
  • src/capabilities/builtins/styles.test.ts
  • src/capabilities/builtins/styles.ts
  • src/capabilities/builtins/theme.test.ts
  • src/capabilities/builtins/theme.ts
  • src/capabilities/builtins/widgets.test.ts
  • src/capabilities/builtins/widgets.ts
  • src/capabilities/stagedEdit.test.ts
  • src/capabilities/stagedEdit.ts
  • src/capabilities/types.ts
  • src/components/common/AppConfirm.vue
  • src/components/common/MkMfm.vue
  • src/components/deck/DeckAiColumn.vue
  • src/components/window/AboutContent.vue
  • src/components/window/CssEditorContent.dom.test.ts
  • src/components/window/CssEditorContent.vue
  • src/components/window/EditHistoryContent.vue
  • src/components/window/PluginsContent.dom.test.ts
  • src/components/window/PluginsContent.vue
  • src/components/window/SkillEditContent.vue
  • src/components/window/ThemeEditorContent.dom.test.ts
  • src/components/window/ThemeEditorContent.vue
  • src/components/window/WidgetEditContent.dom.test.ts
  • src/components/window/WidgetEditContent.vue
  • src/composables/useCodeScheme.ts
  • src/composables/useEditHistoryWindow.ts
  • src/composables/useEditTargetText.test.ts
  • src/composables/useEditTargetText.ts
  • src/composables/useExternalEditSync.test.ts
  • src/composables/useExternalEditSync.ts
  • src/main.ts
  • src/services/editHistory.test.ts
  • src/services/editHistory.ts
  • src/services/selfEditApply.test.ts
  • src/services/selfEditApply.ts
  • src/stores/misstore.test.ts
  • src/stores/misstore.ts
  • src/stores/plugins.test.ts
  • src/stores/plugins.ts
  • src/stores/skills.ts
  • src/stores/widgets.test.ts
  • src/stores/widgets.ts
  • src/stores/windows.ts
  • src/styles/global.css
  • src/utils/highlight.ts
  • src/utils/simpleMarkdown.dom.test.ts
  • src/utils/simpleMarkdown.ts
  • src/windows/exposure.test.ts
  • src/windows/registry.ts

Comment thread DEVELOPMENT.md
**編集履歴 + revert:**
- skill / widget / plugin / theme の各カテゴリで `*.history` / `*.revert` capability を提供
- skill / widget / plugin / theme / カスタム CSS の各カテゴリで `*.history` / `*.revert` capability を提供
- 編集前のスナップショットをリング (10 件) で sidecar 管理 (`src/utils/historyFs.ts`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the hard-coded snapshot count from the root document.

Line 721 includes the changeable value 10 件. Reference src/utils/historyFs.ts as the source of truth instead. If the exact count must remain, add docs-lint-disable-next-line with the reason.

As per coding guidelines, リポジトリ直下のドキュメントでは、変化しやすい数値や行番号を書かず、正本ファイルへの参照を記載する。例外には docs-lint-disable-next-line と理由を付ける.

🤖 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 `@DEVELOPMENT.md` at line 721, Remove the hard-coded “10件” snapshot count from
the root document’s description and refer readers to src/utils/historyFs.ts as
the source of truth. If retaining the count is required, add
docs-lint-disable-next-line with a clear reason.

Source: Coding guidelines

Comment on lines +544 to +550
const next = takeStagedEdit(
ctx,
'plugins.revert',
plugin.src,
() => entry.snapshot.src,
)
store.updateSrc(installId, next)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restart an active plugin after the revert.

store.updateSrc() persists the reverted source, but the running active plugin keeps its previous source. plugins.update already relaunches active plugins after the same store update. Reload the updated plugin after Line 550.

Proposed fix
     )
     store.updateSrc(installId, next)
+    const updated = store.getPlugin(installId)
+    if (updated?.active) {
+      await launchPlugin(updated)
+    }
     return { installId, reverted: true, at: entry.at }
📝 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.

Suggested change
const next = takeStagedEdit(
ctx,
'plugins.revert',
plugin.src,
() => entry.snapshot.src,
)
store.updateSrc(installId, next)
const next = takeStagedEdit(
ctx,
'plugins.revert',
plugin.src,
() => entry.snapshot.src,
)
store.updateSrc(installId, next)
const updated = store.getPlugin(installId)
if (updated?.active) {
await launchPlugin(updated)
}
return { installId, reverted: true, at: entry.at }
🤖 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/capabilities/builtins/plugins.ts` around lines 544 - 550, After
store.updateSrc(installId, next) in the plugins.revert flow, invoke the existing
plugins.update relaunch behavior for the affected plugin so an active plugin
restarts with the reverted source; preserve the current revert and persistence
logic for inactive plugins.

Comment on lines +52 to +57
async function reload() {
loading.value = true
entries.value = await listSnapshots(props.kind, props.basename)
if (selected.value >= entries.value.length) selected.value = 0
loading.value = false
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle failures of listSnapshots and dispatchCapability.

reload sets loading to true and awaits listSnapshots. If the read fails, the rejection escapes the watch callback, loading stays true, and the window shows "読み込み中…" forever. revert has the same shape: if dispatchCapability throws, reverting stays true and the button stays disabled.

Wrap both bodies in try/finally and show the error in revertError or a load error state.

🛡️ Proposed fix
 async function reload() {
   loading.value = true
-  entries.value = await listSnapshots(props.kind, props.basename)
-  if (selected.value >= entries.value.length) selected.value = 0
-  loading.value = false
+  try {
+    entries.value = await listSnapshots(props.kind, props.basename)
+    if (selected.value >= entries.value.length) selected.value = 0
+  } catch (e) {
+    entries.value = []
+    revertError.value = e instanceof Error ? e.message : '履歴の読み込みに失敗しました'
+  } finally {
+    loading.value = false
+  }
 }
@@
 async function revert(index: number) {
   reverting.value = true
   revertError.value = ''
-  const result = await dispatchCapability(
-    spec.value.revertCapabilityId,
-    spec.value.revertParams(props.itemId ?? '', index),
-    { principal: { kind: 'user' } },
-  )
-  reverting.value = false
+  let result
+  try {
+    result = await dispatchCapability(
+      spec.value.revertCapabilityId,
+      spec.value.revertParams(props.itemId ?? '', index),
+      { principal: { kind: 'user' } },
+    )
+  } catch (e) {
+    revertError.value = e instanceof Error ? e.message : '戻す操作に失敗しました'
+    return
+  } finally {
+    reverting.value = false
+  }
   if (!result.ok && result.code !== 'user_cancelled') {

Note: revertError is declared after reload in the source order. Move the revertError declaration above reload if you reuse it there.

Also applies to: 79-95

🤖 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/components/window/EditHistoryContent.vue` around lines 52 - 57, Update
reload and revert to handle rejected listSnapshots and dispatchCapability calls:
wrap each operation in try/finally so loading and reverting are always reset,
and record failures in the existing load-error state or revertError. Move
revertError above reload if reload uses it, while preserving successful snapshot
selection and revert behavior.

Comment thread src/stores/misstore.ts
Comment on lines +985 to +1021
async function confirmWidgetUpdate(
existing: WidgetMeta,
fetched: { source: string; hash: string; entry: StoreWidgetEntry },
opts: { alwaysConfirm: boolean },
): Promise<WidgetMeta | undefined | false> {
const { source, hash, entry: e } = fetched
if (opts.alwaysConfirm || source !== existing.src) {
const ok = await useConfirm().confirm({
title: 'ウィジェットを更新',
message: updateConfirmMessage(existing.name || e.name, e),
okLabel: '更新',
diff: { old: existing.src, new: source, language: 'aiscript' },
})
if (!ok) return false
widgetsStore.applyStoreUpdate(existing.installId, {
src: source,
iconUrl: e.iconUrl,
storeSha512: hash,
storeVersion: e.version,
}
return useWidgetsStore().applyStoreUpdate(existing.installId, {
src: source,
iconUrl: e.iconUrl,
storeSha512: hash,
storeVersion: e.version,
})
}

async function updateWidget(entry: StoreWidgetEntry): Promise<boolean> {
const widgetsStore = useWidgetsStore()
const existing = widgetsStore.widgets.find((w) => w.storeId === entry.id)
if (!existing) return false
installingWidget.value = entry.id
try {
const fetched = await fetchVerifiedSource(
entry,
refetchWidgetEntry(entry.id),
)
const applied = await confirmWidgetUpdate(existing, fetched, {
alwaysConfirm: true,
})
return true
return applied !== false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the return contract of widgets applyStoreUpdate.
ast-grep run --pattern 'function applyStoreUpdate($$$) { $$$ }' --lang typescript src/stores/widgets.ts
rg -n -C8 'applyStoreUpdate' src/stores/widgets.ts

Repository: notedeck-dev/notedeck

Length of output: 971


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- applyStoreUpdate implementation ---'
sed -n '380,445p' src/stores/widgets.ts
printf '%s\n' '--- misstore update functions ---'
sed -n '950,1045p' src/stores/misstore.ts
printf '%s\n' '--- all applyStoreUpdate references ---'
rg -n -C5 'applyStoreUpdate' src

Repository: notedeck-dev/notedeck

Length of output: 31806


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path

widgets = Path("src/stores/widgets.ts").read_text()
misstore = Path("src/stores/misstore.ts").read_text()

assert "if (!widget) return undefined" in widgets
assert "return applied !== false" in misstore
assert "return applied !== false && applied !== undefined" not in misstore

def current(value):
    return value is not False

def proposed(value):
    return value is not False and value is not None

for value in (False, None, {"installId": "w1"}):
    print(f"value={value!r} current={current(value)} proposed={proposed(value)}")
PY

Repository: notedeck-dev/notedeck

Length of output: 292


Treat an unapplied update as failure. If applyStoreUpdate returns undefined for a missing widget, applied !== false returns true. Also check applied !== undefined before reporting success.

🤖 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/stores/misstore.ts` around lines 985 - 1021, Update updateWidget to
report success only when confirmWidgetUpdate returns a value that is neither
false nor undefined; replace the applied !== false condition while preserving
cancellation and successful update behavior.

Comment thread src/stores/widgets.ts
Comment on lines +371 to +373
// 履歴キーは対応表の fileBase (未割当 = ファイル未作成なら履歴も無し)。
// 内容が同じ保存では積まない (plugins.updateSrc と同じ理由)
if (widget.fileBase && widget.src !== src) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Prevent duplicate snapshots during unchanged reinstalls.

Line 373 protects only updateSrc. applyStoreUpdate still calls pushSnapshot when patch.src equals widget.src. confirmWidgetUpdate can call that path for an alwaysConfirm reinstall. A no-op reinstall can add duplicate history entries and evict useful entries from the history ring.

Add the same source comparison to applyStoreUpdate and cover that path with a test.

Proposed fix
-    if (widget.fileBase && !widget.readOnly) {
+    if (widget.fileBase && !widget.readOnly && widget.src !== patch.src) {
🤖 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/stores/widgets.ts` around lines 371 - 373, Update applyStoreUpdate to
call pushSnapshot only when patch.src differs from the current widget.src,
matching the guard in updateSrc and preventing unchanged alwaysConfirm
reinstalls from adding history entries. Add a test covering
confirmWidgetUpdate/applyStoreUpdate with an unchanged source and verify no
duplicate snapshot is recorded.

Comment thread src/styles/global.css
Comment on lines +124 to +128
/* シンタックストークン (CodeMirror 側)。Shiki 側は色ごとのクラスを
assets/shiki-*.css が持つので、こちらはエディタ用 */
--nd-codeKeyword: #569cd6;
--nd-codeString: #ce9178;
--nd-codeNumber: #b5cea8;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove the duplicate custom properties.

Stylelint rejects --nd-codeString and --nd-codeNumber because :root already defines both properties. Keep one dark-scheme declaration for each property, then retain the light-scheme overrides in the later selector.

🧰 Tools
🪛 Stylelint (17.14.0)

[error] 127-127: Duplicate custom property "--nd-codeString" (declaration-block-no-duplicate-custom-properties)

(declaration-block-no-duplicate-custom-properties)


[error] 128-128: Duplicate custom property "--nd-codeNumber" (declaration-block-no-duplicate-custom-properties)

(declaration-block-no-duplicate-custom-properties)

🤖 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/styles/global.css` around lines 124 - 128, Remove the duplicate
--nd-codeString and --nd-codeNumber declarations from the shown dark-scheme
block in global.css, retaining a single dark-scheme definition for each and
preserving the later light-scheme overrides. Leave the --nd-codeKeyword
declaration unchanged unless it is also duplicated.

Source: Linters/SAST tools

@hitalin
hitalin merged commit 8e314a5 into main Aug 12, 2026
22 checks passed
This was referenced Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📖Doc Documentation related issue/PR javascript Pull requests that update javascript code rust Pull requests that update rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: AI の編集を承認する前に差分で見せる

1 participant