akari-video 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/akari.mjs +8 -3
- package/package.json +1 -1
- package/src/cli.mjs +51 -13
- package/src/messages.mjs +79 -8
- package/src/self-update.mjs +4 -0
- package/src/status-command.mjs +10 -2
- package/src/update-check.mjs +103 -23
- package/vendor/.akari-capability-sources.json +1 -0
- package/vendor/docs/contract-2026-07-17-data-contract-versioning.md +6 -0
- package/vendor/docs/contract-2026-07-25-r6-audio-tracks-and-trim.md +1 -1
- package/vendor/docs/contract-2026-08-23-stroke-persistence.md +75 -0
- package/vendor/packages/akari-launcher/package.json +1 -1
- package/vendor/packages/decision-cards/README.md +12 -0
- package/vendor/packages/decision-cards/package.json +3 -2
- package/vendor/packages/edit-lint/src/edit-lint.mjs +191 -389
- package/vendor/packages/edit-store/lib/cut-adjacency.d.ts +19 -0
- package/vendor/packages/edit-store/lib/cut-adjacency.js +28 -0
- package/vendor/packages/edit-store/lib/edit-store.d.ts +13 -4
- package/vendor/packages/edit-store/lib/edit-store.js +62 -45
- package/vendor/packages/edit-store/lib/edit-v2-item-write.d.ts +53 -0
- package/vendor/packages/edit-store/lib/edit-v2-item-write.js +162 -0
- package/vendor/packages/edit-store/lib/edit-v2.d.ts +45 -4
- package/vendor/packages/edit-store/lib/edit-v2.js +72 -1
- package/vendor/packages/edit-store/lib/index.d.ts +3 -0
- package/vendor/packages/edit-store/lib/index.js +3 -0
- package/vendor/packages/edit-store/lib/internal-model.d.ts +9 -0
- package/vendor/packages/edit-store/lib/internal-model.js +381 -41
- package/vendor/packages/edit-store/lib/migrate/index.js +226 -19
- package/vendor/packages/edit-store/lib/migrate/legacy-parse.js +5 -1
- package/vendor/packages/edit-store/lib/retime.js +14 -0
- package/vendor/packages/edit-store/lib/track-transition-compatibility.d.ts +23 -0
- package/vendor/packages/edit-store/lib/track-transition-compatibility.js +76 -0
- package/vendor/packages/edit-store/lib/write-gate.d.ts +2 -0
- package/vendor/packages/edit-store/lib/write-gate.js +11 -1
- package/vendor/packages/overlay-runtime/package.json +2 -1
- package/vendor/packages/pen-visuals/package.json +1 -0
- package/vendor/packages/schemas/bin/validate-research-plan.mjs +55 -7
- package/vendor/packages/schemas/edit.schema.json +53 -3
- package/vendor/packages/schemas/examples/edit-v2-audio-track-valid/edit.json +56 -0
- package/vendor/packages/schemas/examples/edit-v2-valid/edit.json +38 -2
- package/vendor/packages/schemas/fixtures/review/valid-strokes/review.json +1 -1
- package/vendor/packages/schemas/research-plan.schema.json +24 -2
- package/vendor/packages/schemas/review.schema.json +18 -0
- package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +106 -5
- package/vendor/packages/schemas/test/fixtures/research-plan/invalid-nested-cutaway/research-plan.json +25 -0
- package/vendor/packages/schemas/test/fixtures/research-plan/valid-legacy-without-shot-ids/research-plan.json +29 -0
- package/vendor/packages/schemas/test/fixtures/research-plan/valid-visual-storyboard/research-plan.json +72 -0
- package/vendor/packages/schemas/test/validate-research-plan.test.mjs +18 -0
- package/vendor/skills/address-review/bin/list.mjs +12 -0
- package/vendor/skills/address-review/dev-fixtures/fixture-project/review.json +3 -0
- package/vendor/skills/address-review/test/review-store.test.mjs +6 -0
- package/vendor/skills/compile-review-session/bin/core/compiler.mjs +5 -0
- package/vendor/skills/compile-review-session/test/compiler.test.mjs +9 -0
- package/vendor/skills/compile-review-session/test/ui-events.test.mjs +1 -0
- package/vendor/skills/edit-plan/SKILL.md +1 -1
- package/vendor/skills/edit-plan/beat-sync.md +18 -4
- package/vendor/skills/edit-plan/beats.md +1 -1
- package/vendor/skills/edit-plan/emphasis-detection.md +1 -1
- package/vendor/skills/edit-plan/execution.md +1 -1
- package/vendor/skills/research-plan/SKILL.md +1 -1
- package/vendor/skills/research-plan/storyboard.md +30 -1
package/bin/akari.mjs
CHANGED
|
@@ -11,14 +11,19 @@ import { runCapabilityCommand } from '../src/capability-command.mjs';
|
|
|
11
11
|
import { runStoreCommand } from '../src/store-command.mjs';
|
|
12
12
|
import { runAssetsCommand } from '../src/assets-command.mjs';
|
|
13
13
|
import { runMigrateCommand } from '../src/migrate-command.mjs';
|
|
14
|
-
import { maybeApplyPendingUpdateOnLaunch,
|
|
15
|
-
import { describeCliHelp } from '../src/messages.mjs';
|
|
14
|
+
import { maybeApplyPendingUpdateOnLaunch, resolveInstalledVersionInfo } from '../src/update-check.mjs';
|
|
15
|
+
import { describeCliHelp, describeInstalledVersions } from '../src/messages.mjs';
|
|
16
16
|
|
|
17
17
|
// `akari --version` / `-v`: インストール済みの版を表示するだけの最小コマンド
|
|
18
18
|
// (タスク契約 2026-08-11-update-u4-cli-self-update の受け入れ条件 —
|
|
19
19
|
// `akari update` / `--rollback` 後にインストール先の版を観測する手段として必要)。
|
|
20
20
|
async function printVersion() {
|
|
21
|
-
|
|
21
|
+
const versionInfo = resolveInstalledVersionInfo({ env: process.env });
|
|
22
|
+
// 1 行目は update / rollback の既存機械観測契約として CLI 版だけを維持する。
|
|
23
|
+
console.log(`v${versionInfo.cliVersion}`);
|
|
24
|
+
for (const line of describeInstalledVersions(versionInfo)) {
|
|
25
|
+
console.log(line);
|
|
26
|
+
}
|
|
22
27
|
return { exitCode: 0 };
|
|
23
28
|
}
|
|
24
29
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
|
6
6
|
import { detectProjectState } from './project-state.mjs';
|
|
7
7
|
import { findClaudeExecutable, findOpencodeExecutable } from './path-lookup.mjs';
|
|
8
8
|
import { loadTaskLabels } from './task-labels.mjs';
|
|
9
|
-
import { describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
|
|
9
|
+
import { describeForceReinstall, describeInstalledVersions, describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
|
|
10
10
|
import { resolveEffectiveProjectRoot } from './first-run.mjs';
|
|
11
11
|
import { maybeShowAssetIntroNotice } from './sounds-setup.mjs';
|
|
12
12
|
import {
|
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
readCacheSync,
|
|
17
17
|
readOwnVersion,
|
|
18
18
|
recordDismissalSync,
|
|
19
|
+
refreshUpdateFeed,
|
|
19
20
|
resolveCachePath,
|
|
21
|
+
resolveInstalledVersionInfo,
|
|
20
22
|
triggerBackgroundRefresh
|
|
21
23
|
} from './update-check.mjs';
|
|
22
24
|
import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self-update.mjs';
|
|
@@ -41,7 +43,8 @@ export async function run(args, options = {}) {
|
|
|
41
43
|
const spawnOpencode = options.spawnOpencode ?? defaultSpawnOpencode;
|
|
42
44
|
const env = options.env ?? process.env;
|
|
43
45
|
const platform = options.platform ?? process.platform;
|
|
44
|
-
const
|
|
46
|
+
const versionInfo = resolveCommandVersionInfo(options, env);
|
|
47
|
+
const currentVersion = versionInfo.currentVersion;
|
|
45
48
|
const now = options.now ?? new Date();
|
|
46
49
|
|
|
47
50
|
// --opencode / --claude / --claudecode / --yes / --here フラグを解析
|
|
@@ -92,13 +95,13 @@ export async function run(args, options = {}) {
|
|
|
92
95
|
} catch (error) {
|
|
93
96
|
log(`接続確認でエラーが発生しました(続行します): ${error instanceof Error ? error.message : String(error)}`);
|
|
94
97
|
}
|
|
95
|
-
log(describeVersionStatus(
|
|
98
|
+
log(describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env))));
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
// 新版通知(契約 §4-1): キャッシュの読み比較のみ・ネットワークには一切触れない
|
|
99
102
|
// (起動をブロックしない)。fetch は detached な子プロセスへ切り離し、
|
|
100
103
|
// 結果は次回セッションで効く。
|
|
101
|
-
const updateNotice = formatUpdateNotice((options.checkUpdate ?? checkForUpdateSync)({ currentVersion, env }));
|
|
104
|
+
const updateNotice = formatUpdateNotice((options.checkUpdate ?? checkForUpdateSync)({ currentVersion, versionInfo, env }));
|
|
102
105
|
if (updateNotice) {
|
|
103
106
|
log(updateNotice);
|
|
104
107
|
}
|
|
@@ -178,22 +181,26 @@ function defaultSpawnOpencode(opencodePath, args, projectRoot) {
|
|
|
178
181
|
/**
|
|
179
182
|
* `akari update`: 新版があり、かつ自己更新の対象(app 経由実行 + フィードに
|
|
180
183
|
* `components.app` がある)なら DL → sha256 検証 → 適用まで実行する(契約 §11。
|
|
184
|
+
* `app/.akari-install-ref` がある管理インストールは、npm 側 CLI から実行した場合も対象。
|
|
181
185
|
* 「update は明示操作」なので U2 の沈黙原則は適用せず、失敗は必ず表示する)。
|
|
182
186
|
* 対象外(npm グローバル / git checkout・旧フィード)のときは従来どおり
|
|
183
187
|
* **案内するだけ**(自動実行はしない — 契約 §4-1)に縮退する。`--dismiss` は
|
|
184
188
|
* 自己更新を試みず、キャッシュに載っている最新版の通知を今後出さないよう記録するだけ
|
|
185
|
-
* (既存挙動を維持)。`--rollback` は直前 1
|
|
186
|
-
*
|
|
187
|
-
*
|
|
189
|
+
* (既存挙動を維持)。`--force` は同じ版の本体も再導入し、`--rollback` は直前 1 世代
|
|
190
|
+
* (`~/.akari/app-previous/`)へ戻す。
|
|
191
|
+
* 通常時にネットワークへ触れるのは自己更新の DL 区間のみ。`--force` だけはキャッシュが
|
|
192
|
+
* 未取得なら、復旧経路を塞がないためフィードの同期取得を 1 回試す。
|
|
188
193
|
*/
|
|
189
194
|
export async function runUpdateCommand(args, options = {}) {
|
|
190
195
|
const log = options.log ?? ((line) => console.log(line));
|
|
191
196
|
const env = options.env ?? process.env;
|
|
192
|
-
const
|
|
197
|
+
const versionInfo = resolveCommandVersionInfo(options, env);
|
|
198
|
+
const currentVersion = versionInfo.currentVersion;
|
|
193
199
|
const cachePath = resolveCachePath(env);
|
|
194
|
-
|
|
200
|
+
let cache = readCacheSync(cachePath);
|
|
195
201
|
const dismissRequested = args.includes('--dismiss');
|
|
196
202
|
const rollbackRequested = args.includes('--rollback');
|
|
203
|
+
const forceRequested = args.includes('--force');
|
|
197
204
|
|
|
198
205
|
if (rollbackRequested) {
|
|
199
206
|
return (options.rollbackSelfUpdate ?? rollbackSelfUpdate)({ env, log });
|
|
@@ -206,26 +213,41 @@ export async function runUpdateCommand(args, options = {}) {
|
|
|
206
213
|
dismissed = true;
|
|
207
214
|
}
|
|
208
215
|
const finalCache = dismissed ? readCacheSync(cachePath) : cache;
|
|
209
|
-
for (const line of describeUpdateCommand({ currentVersion, cache: finalCache, dismissed })) {
|
|
216
|
+
for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache: finalCache, dismissed })) {
|
|
210
217
|
log(line);
|
|
211
218
|
}
|
|
212
219
|
return { exitCode: 0 };
|
|
213
220
|
}
|
|
214
221
|
|
|
222
|
+
if (forceRequested && !cache?.feed) {
|
|
223
|
+
await (options.refreshUpdateFeed ?? refreshUpdateFeed)({ env, fetchImpl: options.fetchImpl });
|
|
224
|
+
cache = readCacheSync(cachePath);
|
|
225
|
+
}
|
|
226
|
+
|
|
215
227
|
const feed = cache?.feed;
|
|
216
228
|
const updateAvailable = isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) > 0;
|
|
217
|
-
const
|
|
229
|
+
const reinstallRequested = forceRequested && isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) >= 0;
|
|
230
|
+
const hasManagedApp = versionInfo.managedApp === true;
|
|
231
|
+
const selfUpdateEligible = (updateAvailable || reinstallRequested)
|
|
218
232
|
&& !!feed.components?.app?.url
|
|
219
233
|
&& !!feed.components?.app?.sha256
|
|
220
|
-
&& (options.isRunningFromAppDir ?? isRunningFromAppDir)({ env, launcherRoot: options.launcherRoot });
|
|
234
|
+
&& (hasManagedApp || (options.isRunningFromAppDir ?? isRunningFromAppDir)({ env, launcherRoot: options.launcherRoot }));
|
|
221
235
|
|
|
222
236
|
if (!selfUpdateEligible) {
|
|
223
|
-
for (const line of describeUpdateCommand({ currentVersion, cache, dismissed: false })) {
|
|
237
|
+
for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed: false })) {
|
|
224
238
|
log(line);
|
|
225
239
|
}
|
|
226
240
|
return { exitCode: 0 };
|
|
227
241
|
}
|
|
228
242
|
|
|
243
|
+
for (const line of describeInstalledVersions(versionInfo)) {
|
|
244
|
+
log(line);
|
|
245
|
+
}
|
|
246
|
+
log(`最新バージョン: v${feed.product}`);
|
|
247
|
+
if (forceRequested) {
|
|
248
|
+
log(describeForceReinstall(versionInfo, feed.product));
|
|
249
|
+
}
|
|
250
|
+
|
|
229
251
|
return (options.applySelfUpdate ?? applySelfUpdate)({
|
|
230
252
|
env,
|
|
231
253
|
feed,
|
|
@@ -234,3 +256,19 @@ export async function runUpdateCommand(args, options = {}) {
|
|
|
234
256
|
runNpmInstall: options.runNpmInstall
|
|
235
257
|
});
|
|
236
258
|
}
|
|
259
|
+
|
|
260
|
+
function resolveCommandVersionInfo(options, env) {
|
|
261
|
+
// 既存テスト/埋め込み利用の currentVersion 注入は CLI 版注入としても扱う。
|
|
262
|
+
const cliVersion = options.cliVersion ?? options.currentVersion ?? readOwnVersion();
|
|
263
|
+
if (options.currentVersion !== undefined) {
|
|
264
|
+
const appVersion = options.appVersion ?? null;
|
|
265
|
+
return {
|
|
266
|
+
cliVersion,
|
|
267
|
+
appVersion,
|
|
268
|
+
currentVersion: options.currentVersion,
|
|
269
|
+
source: appVersion ? 'install-ref' : 'cli-fallback',
|
|
270
|
+
mismatch: appVersion !== null && compareVersions(cliVersion, appVersion) !== 0
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return resolveInstalledVersionInfo({ env, cliVersion });
|
|
274
|
+
}
|
package/src/messages.mjs
CHANGED
|
@@ -59,18 +59,83 @@ export function opencodeMissingGuidance() {
|
|
|
59
59
|
*/
|
|
60
60
|
export function formatUpdateNotice(status) {
|
|
61
61
|
if (!status?.available) {
|
|
62
|
+
if (status?.mismatch) {
|
|
63
|
+
return formatVersionMismatch(status);
|
|
64
|
+
}
|
|
62
65
|
return null;
|
|
63
66
|
}
|
|
64
|
-
|
|
67
|
+
const current = status.mismatch
|
|
68
|
+
? `CLI v${status.cliVersion} / 本体 v${status.appVersion} → ${versionRelationLabel(status)}`
|
|
69
|
+
: `現在 v${status.currentVersion}`;
|
|
70
|
+
return `⬆ AKARI Video v${status.latestVersion}${channelSuffix(status.channel)}があります(${current})→ 詳細: akari update`;
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/** `akari doctor` 系出力に足す 1 行(現在版 + フィード取得状態)。 */
|
|
68
|
-
export function describeVersionStatus(
|
|
74
|
+
export function describeVersionStatus(versionOrInfo, cache) {
|
|
75
|
+
if (typeof versionOrInfo === 'string') {
|
|
76
|
+
if (!cache?.feed) {
|
|
77
|
+
return `バージョン: v${versionOrInfo}(更新フィード: 未取得)`;
|
|
78
|
+
}
|
|
79
|
+
const fetchedAt = typeof cache.fetched_at === 'string' ? cache.fetched_at : '不明';
|
|
80
|
+
return `バージョン: v${versionOrInfo}(更新フィード: 取得済み・${fetchedAt} 時点)`;
|
|
81
|
+
}
|
|
82
|
+
const info = normalizeVersionInfo(versionOrInfo);
|
|
83
|
+
const installed = describeInstalledVersions(info).join(' / ');
|
|
69
84
|
if (!cache?.feed) {
|
|
70
|
-
return
|
|
85
|
+
return `${installed}(更新フィード: 未取得)`;
|
|
71
86
|
}
|
|
72
87
|
const fetchedAt = typeof cache.fetched_at === 'string' ? cache.fetched_at : '不明';
|
|
73
|
-
return
|
|
88
|
+
return `${installed}(更新フィード: 取得済み・${fetchedAt} 時点)`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function describeInstalledVersions(versionOrInfo) {
|
|
92
|
+
const info = normalizeVersionInfo(versionOrInfo);
|
|
93
|
+
if (info.installRefNeedsRepair) {
|
|
94
|
+
const installRefPath = info.installRefPath ?? '~/.akari/app/.akari-install-ref';
|
|
95
|
+
return [
|
|
96
|
+
`CLI バージョン: v${info.cliVersion}`,
|
|
97
|
+
`本体版を判定できません(\`${installRefPath}\` が壊れています)。`,
|
|
98
|
+
'修復するには `akari update --force` を実行してください。'
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
if (!info.appVersion) {
|
|
102
|
+
return [
|
|
103
|
+
`現在のバージョン: v${info.currentVersion}`,
|
|
104
|
+
`CLI バージョン: v${info.cliVersion}`,
|
|
105
|
+
`本体バージョン: 未記録(更新判定は CLI v${info.currentVersion} へフォールバック)`
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
const lines = [`CLI バージョン: v${info.cliVersion}`, `本体バージョン: v${info.appVersion}(更新判定の基準)`];
|
|
109
|
+
if (info.mismatch) {
|
|
110
|
+
lines.push(`版のずれ: CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${versionRelationLabel(info)}`);
|
|
111
|
+
}
|
|
112
|
+
return lines;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function describeForceReinstall(versionOrInfo, targetVersion) {
|
|
116
|
+
const info = normalizeVersionInfo(versionOrInfo);
|
|
117
|
+
return info.installRefNeedsRepair
|
|
118
|
+
? `--force: 版を判定できない本体 → v${targetVersion} を入れ直します。`
|
|
119
|
+
: `--force: 本体 v${info.currentVersion} → v${targetVersion} を入れ直します。`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeVersionInfo(value) {
|
|
123
|
+
if (typeof value === 'string') {
|
|
124
|
+
return { cliVersion: value, appVersion: null, currentVersion: value, mismatch: false };
|
|
125
|
+
}
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function versionRelationLabel(info) {
|
|
130
|
+
return compareVersions(info.appVersion, info.cliVersion) < 0 ? '本体が古い' : 'CLI が古い';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatVersionMismatch(info) {
|
|
134
|
+
const relation = versionRelationLabel(info);
|
|
135
|
+
const guidance = relation === '本体が古い'
|
|
136
|
+
? '`akari update` で本体を更新してください。'
|
|
137
|
+
: '`npm i -g akari-video@latest` で CLI を更新してください。';
|
|
138
|
+
return `⚠ CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${relation}。${guidance}`;
|
|
74
139
|
}
|
|
75
140
|
|
|
76
141
|
/**
|
|
@@ -107,8 +172,9 @@ export function creatorRootPromptText(defaultPath) {
|
|
|
107
172
|
* `akari update` の出力本文(複数行)。フィード未取得・最新・新版ありで案内が変わる。
|
|
108
173
|
* `dismissed` は今回の実行で dismiss 記録を書いたかどうか(表示文言の切り替えのみに使う)。
|
|
109
174
|
*/
|
|
110
|
-
export function describeUpdateCommand({ currentVersion, cache, dismissed }) {
|
|
111
|
-
const
|
|
175
|
+
export function describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed }) {
|
|
176
|
+
const info = versionInfo ?? normalizeVersionInfo(currentVersion);
|
|
177
|
+
const lines = versionInfo ? describeInstalledVersions(info) : [`現在のバージョン: v${currentVersion}`];
|
|
112
178
|
const feed = cache?.feed;
|
|
113
179
|
if (!feed) {
|
|
114
180
|
lines.push('最新情報をまだ取得できていません(オフライン、または初回起動直後の可能性があります)。');
|
|
@@ -121,7 +187,12 @@ export function describeUpdateCommand({ currentVersion, cache, dismissed }) {
|
|
|
121
187
|
lines.push(`リリースノート: ${feed.notes_url}`);
|
|
122
188
|
}
|
|
123
189
|
|
|
124
|
-
if (
|
|
190
|
+
if (info.installRefNeedsRepair) {
|
|
191
|
+
lines.push('本体版を判定できないため、更新判定を行いません。');
|
|
192
|
+
return lines;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (compareVersions(feed.product, info.currentVersion) <= 0) {
|
|
125
196
|
lines.push('お使いのバージョンは最新です。');
|
|
126
197
|
return lines;
|
|
127
198
|
}
|
|
@@ -222,7 +293,7 @@ export function describeCliHelp() {
|
|
|
222
293
|
' (引数なし) プロジェクトを開いて AI エージェントを起動(未作成なら自動作成)',
|
|
223
294
|
' store connect アカウント連携(無料の素材パックと購入済み素材が使えるようになる)',
|
|
224
295
|
' sounds 公式音源ライブラリを一括ダウンロード(無料)',
|
|
225
|
-
' update
|
|
296
|
+
' update [--force] 更新を確認する(--force で本体を入れ直す)',
|
|
226
297
|
' status 接続状態を確認する',
|
|
227
298
|
' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
|
|
228
299
|
'',
|
package/src/self-update.mjs
CHANGED
|
@@ -378,6 +378,10 @@ export function rollbackSelfUpdate({ env = process.env, log = () => {} } = {}) {
|
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
const version = readVersionAt(appDir);
|
|
381
|
+
if (version) {
|
|
382
|
+
// install-ref が無い古い app-previous へ戻した場合も、以後の更新判定を本体版基準に保つ。
|
|
383
|
+
writeFileSync(join(appDir, '.akari-install-ref'), `v${version}\n`, 'utf8');
|
|
384
|
+
}
|
|
381
385
|
log(version ? `v${version} へロールバックしました` : 'ロールバックしました');
|
|
382
386
|
return { exitCode: 0, rolledBack: true, version };
|
|
383
387
|
}
|
package/src/status-command.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
serializeWorkspaceStatus,
|
|
9
9
|
formatWorkspaceStatusSummary,
|
|
10
10
|
} from "./status-core/status.mjs";
|
|
11
|
+
import { describeVersionStatus } from "./messages.mjs";
|
|
12
|
+
import { readCacheSync, readOwnVersion, resolveCachePath, resolveInstalledVersionInfo } from "./update-check.mjs";
|
|
11
13
|
|
|
12
14
|
export async function runStatusCommand(argv, options = {}) {
|
|
13
15
|
const log = options.log ?? ((line) => process.stdout.write(line));
|
|
@@ -19,18 +21,24 @@ export async function runStatusCommand(argv, options = {}) {
|
|
|
19
21
|
error(cause instanceof Error ? cause.message : String(cause));
|
|
20
22
|
return { exitCode: 2 };
|
|
21
23
|
}
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
const versionInfo = options.versionInfo ?? resolveInstalledVersionInfo({
|
|
26
|
+
env,
|
|
27
|
+
cliVersion: options.cliVersion ?? readOwnVersion(),
|
|
28
|
+
});
|
|
29
|
+
const versionLine = describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env)));
|
|
22
30
|
if (detectStatusScope(parsed.projectRoot) === "workspace") {
|
|
23
31
|
const workspace = resolveWorkspaceStatus(parsed.projectRoot);
|
|
24
32
|
// fail-safe: a broken root.json falls through to the unchanged project-scope path below.
|
|
25
33
|
if (workspace) {
|
|
26
|
-
log(parsed.json ? serializeWorkspaceStatus(workspace) : `${formatWorkspaceStatusSummary(workspace)}\n`);
|
|
34
|
+
log(parsed.json ? serializeWorkspaceStatus(workspace) : `${versionLine}\n${formatWorkspaceStatusSummary(workspace)}\n`);
|
|
27
35
|
return { exitCode: 0, workspace };
|
|
28
36
|
}
|
|
29
37
|
}
|
|
30
38
|
const status = parsed.full
|
|
31
39
|
? await resolveFullProjectStatus(parsed.projectRoot)
|
|
32
40
|
: resolveProjectStatus(parsed.projectRoot, { mode: "fast" });
|
|
33
|
-
log(parsed.json ? serializeStatus(status) : `${formatStatusOutput(status)}\n`);
|
|
41
|
+
log(parsed.json ? serializeStatus(status) : `${versionLine}\n${formatStatusOutput(status)}\n`);
|
|
34
42
|
return { exitCode: status.state_health === "inconclusive" ? 1 : 0, status };
|
|
35
43
|
}
|
|
36
44
|
|
package/src/update-check.mjs
CHANGED
|
@@ -55,12 +55,63 @@ export function resolveCachePath(env = process.env) {
|
|
|
55
55
|
return join(resolveAkariHome(env), 'update-check.json');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function resolveInstallRefPath(env = process.env) {
|
|
59
|
+
return join(resolveAkariHome(env), 'app', '.akari-install-ref');
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
/** launcher 自身の package.json version(D3 裁定により現状はプロダクト版と一致)。 */
|
|
59
63
|
export function readOwnVersion() {
|
|
60
64
|
const raw = readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8');
|
|
61
65
|
return JSON.parse(raw).version;
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
/** install-ref を「有効・未記録・破損」の 3 状態で読む内部表現。 */
|
|
69
|
+
function readInstalledAppVersionInfo(env = process.env) {
|
|
70
|
+
const path = resolveInstallRefPath(env);
|
|
71
|
+
try {
|
|
72
|
+
const raw = readFileSync(path, 'utf8').trim();
|
|
73
|
+
const match = raw.match(/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/);
|
|
74
|
+
return match
|
|
75
|
+
? { status: 'valid', version: match[1], path }
|
|
76
|
+
: { status: 'invalid', version: null, path };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
status: error?.code === 'ENOENT' ? 'missing' : 'invalid',
|
|
80
|
+
version: null,
|
|
81
|
+
path
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 実際に render-cut / edit-lint を実行する本体の導入版。
|
|
88
|
+
* install.sh / self-update.mjs が書く `vX.Y.Z` を読む。互換 API として版または null を返し、
|
|
89
|
+
* 未記録と破損の区別は `resolveInstalledVersionInfo()` が保持する。
|
|
90
|
+
*/
|
|
91
|
+
export function readInstalledAppVersion(env = process.env) {
|
|
92
|
+
return readInstalledAppVersionInfo(env).version;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 更新判定に使う版と、CLI / 本体のずれを一度に解決する。 */
|
|
96
|
+
export function resolveInstalledVersionInfo({ env = process.env, cliVersion = readOwnVersion() } = {}) {
|
|
97
|
+
const installRef = readInstalledAppVersionInfo(env);
|
|
98
|
+
const installedAppVersion = installRef.version;
|
|
99
|
+
const currentVersion = installedAppVersion ?? cliVersion;
|
|
100
|
+
return {
|
|
101
|
+
cliVersion,
|
|
102
|
+
appVersion: installedAppVersion,
|
|
103
|
+
currentVersion,
|
|
104
|
+
source: installRef.status === 'valid'
|
|
105
|
+
? 'install-ref'
|
|
106
|
+
: installRef.status === 'invalid' ? 'invalid-install-ref' : 'cli-fallback',
|
|
107
|
+
installRefStatus: installRef.status,
|
|
108
|
+
installRefPath: installRef.path,
|
|
109
|
+
installRefNeedsRepair: installRef.status === 'invalid',
|
|
110
|
+
managedApp: installRef.status !== 'missing',
|
|
111
|
+
mismatch: installedAppVersion !== null && compareVersions(cliVersion, installedAppVersion) !== 0
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
64
115
|
/** "major.minor.patch" の先頭 3 要素だけを数値比較する(prerelease 考慮不要 — 契約 D4: stable のみ)。 */
|
|
65
116
|
export function compareVersions(a, b) {
|
|
66
117
|
const pa = parseVersionTriplet(a);
|
|
@@ -104,23 +155,35 @@ function writeCacheSync(cachePath, cache) {
|
|
|
104
155
|
/**
|
|
105
156
|
* キャッシュと現在版から、新版通知を出すべきかを判定する(同期・純粋関数・I/O なし)。
|
|
106
157
|
*/
|
|
107
|
-
export function evaluateUpdateStatus({ currentVersion, cache }) {
|
|
158
|
+
export function evaluateUpdateStatus({ currentVersion, cache, cliVersion = currentVersion, appVersion = null, source = 'cli-fallback', installRefStatus, installRefPath, installRefNeedsRepair, managedApp, mismatch = false }) {
|
|
159
|
+
const versionDetails = {
|
|
160
|
+
currentVersion,
|
|
161
|
+
cliVersion,
|
|
162
|
+
appVersion,
|
|
163
|
+
source,
|
|
164
|
+
installRefStatus,
|
|
165
|
+
installRefPath,
|
|
166
|
+
installRefNeedsRepair,
|
|
167
|
+
managedApp,
|
|
168
|
+
mismatch
|
|
169
|
+
};
|
|
108
170
|
const feed = cache?.feed;
|
|
109
171
|
if (!isValidFeedShape(feed)) {
|
|
110
|
-
return { available: false };
|
|
172
|
+
return { available: false, ...versionDetails };
|
|
111
173
|
}
|
|
112
174
|
const latest = feed.product;
|
|
113
175
|
if (compareVersions(latest, currentVersion) <= 0) {
|
|
114
|
-
return { available: false };
|
|
176
|
+
return { available: false, latestVersion: latest, ...versionDetails };
|
|
115
177
|
}
|
|
116
178
|
const dismissedAt = cache?.dismissed?.[latest];
|
|
117
|
-
|
|
118
|
-
|
|
179
|
+
// CLI と本体の取り残しは release 通知の dismiss より強い。黙って古い本体を使わせない。
|
|
180
|
+
if (dismissedAt && !mismatch) {
|
|
181
|
+
return { available: false, dismissed: true, latestVersion: latest, ...versionDetails };
|
|
119
182
|
}
|
|
120
183
|
return {
|
|
121
184
|
available: true,
|
|
122
185
|
latestVersion: latest,
|
|
123
|
-
|
|
186
|
+
...versionDetails,
|
|
124
187
|
channel: typeof feed.channel === 'string' ? feed.channel : undefined,
|
|
125
188
|
notesUrl: typeof feed.notes_url === 'string' ? feed.notes_url : undefined,
|
|
126
189
|
cli: feed.components?.cli
|
|
@@ -131,9 +194,13 @@ export function evaluateUpdateStatus({ currentVersion, cache }) {
|
|
|
131
194
|
* `akari` 起動時の同期チェック。ファイルの読み比較のみ(ネットワーク I/O 無し)。
|
|
132
195
|
* fetch 関数への参照すら持たない — 「起動をブロックしない」を構造で保証する。
|
|
133
196
|
*/
|
|
134
|
-
export function checkForUpdateSync({ currentVersion, env = process.env } = {}) {
|
|
197
|
+
export function checkForUpdateSync({ currentVersion, versionInfo, env = process.env } = {}) {
|
|
135
198
|
const cache = readCacheSync(resolveCachePath(env));
|
|
136
|
-
|
|
199
|
+
const versions = versionInfo
|
|
200
|
+
?? (currentVersion === undefined
|
|
201
|
+
? resolveInstalledVersionInfo({ env })
|
|
202
|
+
: { cliVersion: currentVersion, appVersion: null, currentVersion, source: 'cli-fallback', mismatch: false });
|
|
203
|
+
return evaluateUpdateStatus({ ...versions, currentVersion: currentVersion ?? versions.currentVersion, cache });
|
|
137
204
|
}
|
|
138
205
|
|
|
139
206
|
/** 「この版の通知を今後出さない」を記録する(同期・ローカル I/O のみ)。 */
|
|
@@ -157,6 +224,7 @@ export function recordDismissalSync({ version, env = process.env, now = new Date
|
|
|
157
224
|
*
|
|
158
225
|
* `launcherRoot` はテスト用の注入口(`isRunningFromAppDir` へそのまま渡す。
|
|
159
226
|
* `runUpdateCommand` の `options.launcherRoot` と同じ流儀)。
|
|
227
|
+
* app 外から動く npm CLI でも install-ref があれば管理本体を staging 対象にする。
|
|
160
228
|
*/
|
|
161
229
|
export async function maybeStageInBackground({ env = process.env, feed, fetchImpl = globalThis.fetch, timeoutMs, extract, launcherRoot } = {}) {
|
|
162
230
|
if (env.AKARI_NO_AUTO_UPDATE === '1') {
|
|
@@ -166,26 +234,19 @@ export async function maybeStageInBackground({ env = process.env, feed, fetchImp
|
|
|
166
234
|
if (!appComponent?.url || !appComponent?.sha256) {
|
|
167
235
|
return null;
|
|
168
236
|
}
|
|
169
|
-
if (compareVersions(feed.product,
|
|
237
|
+
if (compareVersions(feed.product, resolveInstalledVersionInfo({ env }).currentVersion) <= 0) {
|
|
170
238
|
return null;
|
|
171
239
|
}
|
|
172
|
-
|
|
240
|
+
// npm 側 CLI と本体が分離していても、install-ref があれば管理対象の本体を更新できる。
|
|
241
|
+
if (!isRunningFromAppDir({ env, launcherRoot }) && !readInstalledAppVersion(env)) {
|
|
173
242
|
return null;
|
|
174
243
|
}
|
|
175
244
|
// 沈黙原則(U2 を継承): staging の失敗メッセージはどこにも表示しない(log は no-op)。
|
|
176
245
|
return stageSelfUpdate({ env, feed, log: () => {}, fetchImpl, timeoutMs, extract });
|
|
177
246
|
}
|
|
178
247
|
|
|
179
|
-
/**
|
|
180
|
-
|
|
181
|
-
* すべて沈黙して return する(何も throw しない)。`dismissed` は既存キャッシュから
|
|
182
|
-
* 引き継ぐ(fetch のたびに既読状態が消えないように)。
|
|
183
|
-
*
|
|
184
|
-
* フィード取得・キャッシュ反映が成功した後、契約 §11 のバックグラウンド staging も
|
|
185
|
-
* 同じ沈黙原則のもとで試みる(`maybeStageInBackground`)。成功したときだけ
|
|
186
|
-
* キャッシュへ `staged`(版・sha256・staged_at)を追記する。
|
|
187
|
-
*/
|
|
188
|
-
export async function runBackgroundFetch({ env = process.env, fetchImpl = globalThis.fetch, launcherRoot } = {}) {
|
|
248
|
+
/** フィードを 1 回取得し、正常なら既読状態を保ったままキャッシュへ反映する。 */
|
|
249
|
+
export async function refreshUpdateFeed({ env = process.env, fetchImpl = globalThis.fetch } = {}) {
|
|
189
250
|
const feedUrl = resolveFeedUrl(env);
|
|
190
251
|
const cachePath = resolveCachePath(env);
|
|
191
252
|
try {
|
|
@@ -202,15 +263,34 @@ export async function runBackgroundFetch({ env = process.env, fetchImpl = global
|
|
|
202
263
|
}
|
|
203
264
|
const feed = await response.json();
|
|
204
265
|
if (!isValidFeedShape(feed)) {
|
|
205
|
-
return;
|
|
266
|
+
return null;
|
|
206
267
|
}
|
|
207
268
|
const existing = readCacheSync(cachePath);
|
|
208
|
-
|
|
269
|
+
const next = {
|
|
209
270
|
schema: CACHE_SCHEMA,
|
|
210
271
|
fetched_at: new Date().toISOString(),
|
|
211
272
|
feed,
|
|
212
273
|
dismissed: existing?.dismissed ?? {}
|
|
213
|
-
}
|
|
274
|
+
};
|
|
275
|
+
writeCacheSync(cachePath, next);
|
|
276
|
+
return next;
|
|
277
|
+
} catch {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* バックグラウンド fetch 本体。フィード取得失敗は沈黙し、成功後は契約 §11 の staging を
|
|
284
|
+
* 同じ沈黙原則で試す。成功したときだけキャッシュへ `staged` を追記する。
|
|
285
|
+
*/
|
|
286
|
+
export async function runBackgroundFetch({ env = process.env, fetchImpl = globalThis.fetch, launcherRoot } = {}) {
|
|
287
|
+
const cachePath = resolveCachePath(env);
|
|
288
|
+
try {
|
|
289
|
+
const refreshed = await refreshUpdateFeed({ env, fetchImpl });
|
|
290
|
+
const feed = refreshed?.feed;
|
|
291
|
+
if (!feed) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
214
294
|
|
|
215
295
|
const staged = await maybeStageInBackground({ env, feed, fetchImpl, launcherRoot });
|
|
216
296
|
if (staged?.ok) {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"docs/contract-2026-08-13-avatar-drive-v0.md",
|
|
41
41
|
"docs/contract-2026-08-14-avatar-vrm-v0.md",
|
|
42
42
|
"docs/contract-2026-08-18-v1-render-parity.md",
|
|
43
|
+
"docs/contract-2026-08-23-stroke-persistence.md",
|
|
43
44
|
"packages/akari-launcher/package.json",
|
|
44
45
|
"packages/akari-launcher/README.md",
|
|
45
46
|
"packages/akari-tools/package.json",
|
|
@@ -91,3 +91,9 @@ edit.json の `version` は変わらないし、edit.json が `version: 1` に
|
|
|
91
91
|
- 本体から外した後のエラー文言は次で固定する。
|
|
92
92
|
|
|
93
93
|
> このプロジェクトは古い形式です。`npx akari-migrate@<版> <dir>` で変換してから開いてください。
|
|
94
|
+
|
|
95
|
+
### 6.1 音声トラック化(2026-08-21・凍結前の最後の追加)
|
|
96
|
+
|
|
97
|
+
task `2026-08-20-v2-audio-tracks` に限り、「機能追加禁止・バグ修正のみ」の明示承認済み・1 回限りの例外として、v0/v1 の `audio.sfx[]`・`audio.narration[]`・`audio.bgm` を v2 の audio lane `tracks[].items[]` へ移す変換を追加した。出力側の `at` / `duration` は整数フレームへ確定し、素材側の `in` / `out` と fade / `bgm.in` は秒のまま保持する。実尺が旧形式だけでは決まらない音声は `duration: 0` センチネルとし、この変換器では ffprobe しない。トップレベル `audio` は `master` が宣言されている場合だけ残す。
|
|
98
|
+
|
|
99
|
+
これは凍結前に許可された最後の機能追加である。今後この変換器へさらに変換能力を足さず、§6 の期限どおり本体から分離・削除する。
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
|
|
67
67
|
## 5. §2 追記 — sfx フェード(audio-clip-fades, 2026-08-18・オーナー裁定「クリップ主義」T2)
|
|
68
68
|
|
|
69
|
-
BGM をクリップ化する裁定(内部リポ `
|
|
69
|
+
BGM をクリップ化する裁定(内部リポ `akari-video-internal` の該当タスク)に伴い、
|
|
70
70
|
「音楽をクリップ(audio.sfx[])として置いても BGM ベッドと同じフェード表現ができる」を
|
|
71
71
|
満たすため、`sfxItem` に optional の `fade_in` / `fade_out`(秒・0 以上)を追加のみ拡張する
|
|
72
72
|
(`version` 不変・`contract-2026-07-17-data-contract-versioning.md` の原則に従う)。
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
lifecycle: implemented
|
|
3
|
+
created: 2026-08-23
|
|
4
|
+
updated: 2026-08-23
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 注釈ストローク永続表示契約
|
|
8
|
+
|
|
9
|
+
- 日付: 2026-08-23
|
|
10
|
+
- 状態: **実装済み**
|
|
11
|
+
- 前提: `contract-2026-08-11-review-session-ui-events.md`、
|
|
12
|
+
`contract-2026-07-20-review-json-v1-annotation-model.md`
|
|
13
|
+
- スコープ: Theia shell の出力プレビュー、review session の `strokes.json` 読み出し、
|
|
14
|
+
compile-review-session と address-review の追跡導線
|
|
15
|
+
|
|
16
|
+
## 1. セッション中の表示
|
|
17
|
+
|
|
18
|
+
- pen / rect は従来どおり正規化座標(プレビューフレーム左上を `(0, 0)`、右下を `(1, 1)`)で
|
|
19
|
+
記録する。表示時に現在の content rect へ写像し直すため、ウィンドウのリサイズと出力比率の
|
|
20
|
+
変更で座標は変わらない。
|
|
21
|
+
- pointerup 後は従来のグロー・きらめき・600 ms フェードをそのまま再生し、その後段の静的
|
|
22
|
+
ビットマップへ同じ正規化図形を残す。新しい録音セッションの開始時に前セッションの表示を
|
|
23
|
+
クリアし、録音終了では消さない。
|
|
24
|
+
- 描線 canvas は非描画モードで `pointer-events: none` とする。ペンまたは四角モードのドラッグ中
|
|
25
|
+
だけ既存どおり入力面になる。
|
|
26
|
+
- 注釈パネルの「描線を表示」チェックは既定 ON。OFF は残留描線と明示的に再表示した描線を隠し、
|
|
27
|
+
データを削除しない。ON に戻すと保持した正規化座標から即時再描画する。
|
|
28
|
+
|
|
29
|
+
## 2. 既存セッションの読み出しと再表示
|
|
30
|
+
|
|
31
|
+
`readReviewSessionStrokes({projectRootUri, sessionId})` は
|
|
32
|
+
`review/sessions/<sessionId>/strokes.json` を読み、次を返す。
|
|
33
|
+
|
|
34
|
+
```jsonc
|
|
35
|
+
{
|
|
36
|
+
"sessionId": "s-0001",
|
|
37
|
+
"strokes": [/* pen / rect。frame と recTStart/recTEnd を保持 */],
|
|
38
|
+
"warnings": []
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- `strokes.json` 欠落は `strokes: []` として正常終了する。
|
|
43
|
+
- 配列ルート、または `version: 1` でない `{strokes:[]}` は旧形式として寛容に読む。
|
|
44
|
+
- JSON 破損、未知要素、値域外要素は描線単位で除外して warning に残す。セッション一覧と他の
|
|
45
|
+
描線を巻き込んで失敗させない。
|
|
46
|
+
- 注釈パネルの各録音済みセッションにある「描線」から再表示できる。表示メッセージには
|
|
47
|
+
`target.tab`(edit URI)と先頭ストロークの `target.recT` を添え、`frame.sourceT/cutIndex` で
|
|
48
|
+
プレビューを同じフレームへシークする。
|
|
49
|
+
|
|
50
|
+
## 3. compile 後の原本参照
|
|
51
|
+
|
|
52
|
+
review.json の data `version` は 0 のまま据え置く。compile-review-session はペアになった pen / rect
|
|
53
|
+
へ、既存フィールドを変えず次の任意フィールドを追加する。
|
|
54
|
+
|
|
55
|
+
```jsonc
|
|
56
|
+
"strokeRefs": [{
|
|
57
|
+
"sessionId": "s-0001",
|
|
58
|
+
"strokeId": "st-0001",
|
|
59
|
+
"sessionRef": "s-0001/st-0001"
|
|
60
|
+
}]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `strokeRefs` は `null`、省略、または 1 件以上の配列。欠落は従来データとして正常である。
|
|
64
|
+
- pen は従来どおり最大 100 点の `strokes[].points` も埋め込み、`strokeRefs` から無加工の原本へ
|
|
65
|
+
戻れる。rect は従来どおり `targetKind:"region"` + `region.box` を埋め込み、同じ `strokeRefs`
|
|
66
|
+
から `strokes.json` 内の rect 原本へ戻れる。
|
|
67
|
+
- address-review の一覧は `strokeRefs` を
|
|
68
|
+
`review/sessions/<sessionId>/strokes.json#<strokeId>` として表示する。
|
|
69
|
+
|
|
70
|
+
## 4. 互換性
|
|
71
|
+
|
|
72
|
+
- 追加フィールドのみを使い、既存フィールドの削除・意味変更・data version bump は行わない。
|
|
73
|
+
- 読み手は未知フィールドを保持し、任意フィールドの欠落を旧データとして扱い、既知より大きい
|
|
74
|
+
data version を推測変換しない。
|
|
75
|
+
- `packages/preview-server` は本契約の対象外であり、WebUI の表示挙動は変更しない。
|