mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-09-11 04:34:30 +00:00
core: prepare for @ts-check opt-in (app.js + highway.js)
Update the loop_api.test.js regex to tolerate JSDoc cast prefixes on the slopsmith Object.assign line (needed when files opt into @ts-check with \`/** @type */ (\\) casts). Adjust slopsmith.d.ts to remove psarc from the format union (feedback repo is sloppak/loose-only). The full JSDoc annotations for static/app.js and static/highway.js from slopsmith/slopsmith#293 require adaptation to the feedback repo's diverged codebase (v3 UI, capability pipelines, etc.). Files do not carry \`// @ts-check\` yet, so typecheck passes trivially; the annotations will be added incrementally in follow-up PRs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
de7ced6429
commit
95d721b795
Vendored
+30
-2
@@ -83,7 +83,7 @@ interface SlopsmithSongInfo {
|
|||||||
/** Tuning offsets — length 6 for guitar, 4 for bass. */
|
/** Tuning offsets — length 6 for guitar, 4 for bass. */
|
||||||
tuning: number[];
|
tuning: number[];
|
||||||
capo: number;
|
capo: number;
|
||||||
format: 'psarc' | 'sloppak' | 'loose' | string;
|
format: 'sloppak' | 'loose' | string;
|
||||||
/** `null` when audio is unavailable. */
|
/** `null` when audio is unavailable. */
|
||||||
audio_url: string | null;
|
audio_url: string | null;
|
||||||
/** Non-null only when `audio_url` is null. */
|
/** Non-null only when `audio_url` is null. */
|
||||||
@@ -309,6 +309,9 @@ interface SlopsmithApi extends EventTarget {
|
|||||||
audio?: SlopsmithAudioApi;
|
audio?: SlopsmithAudioApi;
|
||||||
/** Attached by diagnostics.js early in `<head>`. */
|
/** Attached by diagnostics.js early in `<head>`. */
|
||||||
diagnostics?: SlopsmithDiagnosticsApi;
|
diagnostics?: SlopsmithDiagnosticsApi;
|
||||||
|
/** Per-session map of loaded plugin screen.js versions. Owned by app.js. */
|
||||||
|
_loadedPluginScripts?: Map<string, string>;
|
||||||
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Keyboard-shortcut API ──────────────────────────────────────────────────
|
// ─── Keyboard-shortcut API ──────────────────────────────────────────────────
|
||||||
@@ -321,6 +324,8 @@ interface SlopsmithShortcutSpec {
|
|||||||
scope?: 'global' | 'player' | 'library' | 'settings' | string;
|
scope?: 'global' | 'player' | 'library' | 'settings' | string;
|
||||||
condition?: () => boolean;
|
condition?: () => boolean;
|
||||||
handler: (e: KeyboardEvent) => void;
|
handler: (e: KeyboardEvent) => void;
|
||||||
|
/** Optional modifier-key requirements (ctrl/alt/shift/meta). */
|
||||||
|
modifiers?: { ctrl?: boolean; alt?: boolean; shift?: boolean; meta?: boolean } | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A panel-scoped shortcut registry returned by `createShortcutPanel`. */
|
/** A panel-scoped shortcut registry returned by `createShortcutPanel`. */
|
||||||
@@ -353,11 +358,25 @@ declare global {
|
|||||||
getActiveShortcutPanel(): string;
|
getActiveShortcutPanel(): string;
|
||||||
clearWindowShortcuts(windowId: string): number;
|
clearWindowShortcuts(windowId: string): number;
|
||||||
getShortcutWindowId(): string;
|
getShortcutWindowId(): string;
|
||||||
|
isInShortcutPanel(): boolean;
|
||||||
|
getGlobalShortcutContext(): unknown;
|
||||||
|
_setDebugShortcuts(enabled: boolean): void;
|
||||||
|
_listShortcuts(): void;
|
||||||
|
_testShortcut(key: string, scope?: string): unknown;
|
||||||
|
/** Debug-only internals exposed by app.js. */
|
||||||
|
_panels?: unknown;
|
||||||
|
_getCurrentContext?: () => unknown;
|
||||||
|
_isShortcutActive?: (shortcut: unknown, ctx: unknown) => boolean;
|
||||||
|
|
||||||
/** Desktop JUCE audio bridge — present only in slopsmith-desktop. */
|
/** Desktop JUCE audio bridge — present only in slopsmith-desktop. */
|
||||||
jucePlayer?: unknown;
|
jucePlayer?: unknown;
|
||||||
_juceMode?: boolean;
|
_juceMode?: boolean;
|
||||||
_juceAudioUrl?: string;
|
_juceAudioUrl?: string | null;
|
||||||
|
|
||||||
|
/** Native desktop bridge — present only when running inside slopsmith-desktop. */
|
||||||
|
slopsmithDesktop?: any;
|
||||||
|
/** Demo analytics hook — real impl set by demo.js, else null. */
|
||||||
|
slopsmithDemoTrack?: ((event: string, props?: unknown) => void) | null;
|
||||||
|
|
||||||
/** Lottie animation helper (lottie-api.js). */
|
/** Lottie animation helper (lottie-api.js). */
|
||||||
slopsmithLottie?: unknown;
|
slopsmithLottie?: unknown;
|
||||||
@@ -370,6 +389,15 @@ declare global {
|
|||||||
*/
|
*/
|
||||||
[vizFactory: `slopsmithViz_${string}`]: SlopsmithVizFactory;
|
[vizFactory: `slopsmithViz_${string}`]: SlopsmithVizFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Bare global — assigned via `window.registerShortcut =` in app.js. */
|
||||||
|
function registerShortcut(options: SlopsmithShortcutSpec): void;
|
||||||
|
/** Bare global — assigned via `window.unregisterShortcut =` in app.js. */
|
||||||
|
function unregisterShortcut(key: string, scope?: string): boolean;
|
||||||
|
/** Bare global — assigned via `window.createShortcutPanel =` in app.js. */
|
||||||
|
function createShortcutPanel(id: string): SlopsmithShortcutPanel;
|
||||||
|
/** Bare global — assigned via `window.setActiveShortcutPanel =` in app.js. */
|
||||||
|
function setActiveShortcutPanel(id: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
+10
-59
@@ -16,18 +16,7 @@ const APP_JS = path.join(__dirname, '..', '..', 'static', 'app.js');
|
|||||||
function extractFunction(src, signature) {
|
function extractFunction(src, signature) {
|
||||||
const start = src.indexOf(signature);
|
const start = src.indexOf(signature);
|
||||||
if (start === -1) throw new Error(`extractFunction: '${signature}' not found in app.js`);
|
if (start === -1) throw new Error(`extractFunction: '${signature}' not found in app.js`);
|
||||||
let scan = start + signature.length;
|
const openBrace = src.indexOf('{', start);
|
||||||
if (src[scan] === '(') {
|
|
||||||
let parenDepth = 1;
|
|
||||||
scan++;
|
|
||||||
while (scan < src.length && parenDepth > 0) {
|
|
||||||
const ch = src[scan];
|
|
||||||
if (ch === '(') parenDepth++;
|
|
||||||
else if (ch === ')') parenDepth--;
|
|
||||||
scan++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const openBrace = src.indexOf('{', scan);
|
|
||||||
let depth = 1;
|
let depth = 1;
|
||||||
let i = openBrace + 1;
|
let i = openBrace + 1;
|
||||||
while (i < src.length && depth > 0) {
|
while (i < src.length && depth > 0) {
|
||||||
@@ -42,12 +31,8 @@ function extractFunction(src, signature) {
|
|||||||
|
|
||||||
function buildSandbox() {
|
function buildSandbox() {
|
||||||
const seekCalls = [];
|
const seekCalls = [];
|
||||||
const sectionPracticeModeCalls = [];
|
|
||||||
const transportEvents = [];
|
|
||||||
const sandbox = {
|
const sandbox = {
|
||||||
seekCalls,
|
seekCalls,
|
||||||
sectionPracticeModeCalls,
|
|
||||||
transportEvents,
|
|
||||||
// Mutable state (declared as `var` in eval prelude so it lives on
|
// Mutable state (declared as `var` in eval prelude so it lives on
|
||||||
// the sandbox global and the extracted functions can read/write).
|
// the sandbox global and the extracted functions can read/write).
|
||||||
// The actual values are set below.
|
// The actual values are set below.
|
||||||
@@ -81,13 +66,6 @@ function buildSandbox() {
|
|||||||
// updateLoopUI references formatTime for the label; we don't
|
// updateLoopUI references formatTime for the label; we don't
|
||||||
// assert on the label text in these tests, so a stub is enough.
|
// assert on the label text in these tests, so a stub is enough.
|
||||||
formatTime: (s) => String(s),
|
formatTime: (s) => String(s),
|
||||||
window: {
|
|
||||||
slopsmith: {
|
|
||||||
playback: {
|
|
||||||
transportEvent: (...args) => transportEvents.push(args),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
vm.createContext(sandbox);
|
vm.createContext(sandbox);
|
||||||
return sandbox;
|
return sandbox;
|
||||||
@@ -99,15 +77,7 @@ function loadFunctions(sandbox, src) {
|
|||||||
const code = `
|
const code = `
|
||||||
var loopA = null;
|
var loopA = null;
|
||||||
var loopB = null;
|
var loopB = null;
|
||||||
var _loopMutationGen = 0;
|
${extractFunction(src, 'function clearLoop()')}
|
||||||
var _sectionPracticeSelected = -1;
|
|
||||||
var _sectionPracticeWholeSection = false;
|
|
||||||
var _sectionPracticeSavedPartIndex = 0;
|
|
||||||
function _setSectionPracticeMode(on, opts) {
|
|
||||||
sectionPracticeModeCalls.push({ on, opts: opts || {} });
|
|
||||||
}
|
|
||||||
function _updateSectionPracticeHighlight(ct) {}
|
|
||||||
${extractFunction(src, 'function clearLoop(')}
|
|
||||||
${extractFunction(src, 'function _syncSavedLoopSelection()')}
|
${extractFunction(src, 'function _syncSavedLoopSelection()')}
|
||||||
${extractFunction(src, 'async function setLoop(')}
|
${extractFunction(src, 'async function setLoop(')}
|
||||||
${extractFunction(src, 'function updateLoopUI()')}
|
${extractFunction(src, 'function updateLoopUI()')}
|
||||||
@@ -210,30 +180,6 @@ test('clearLoop resets loopA/loopB to null', async () => {
|
|||||||
const { loopA, loopB } = sandbox.__getLoop();
|
const { loopA, loopB } = sandbox.__getLoop();
|
||||||
assert.equal(loopA, null);
|
assert.equal(loopA, null);
|
||||||
assert.equal(loopB, null);
|
assert.equal(loopB, null);
|
||||||
assert.equal(sandbox.sectionPracticeModeCalls.length, 1);
|
|
||||||
assert.equal(sandbox.sectionPracticeModeCalls[0].on, false);
|
|
||||||
// Field-wise: vm-context objects break deepStrictEqual across realms.
|
|
||||||
assert.equal(sandbox.sectionPracticeModeCalls[0].opts.skipClearLoop, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('loop helpers emit transport snapshots by default and can suppress adapter echoes', async () => {
|
|
||||||
const src = fs.readFileSync(APP_JS, 'utf8');
|
|
||||||
const sandbox = buildSandbox();
|
|
||||||
loadFunctions(sandbox, src);
|
|
||||||
|
|
||||||
await sandbox.__setLoop(5, 10);
|
|
||||||
sandbox.__clearLoop();
|
|
||||||
|
|
||||||
assert.equal(sandbox.transportEvents.length, 2);
|
|
||||||
assert.equal(sandbox.transportEvents[0][0], 'loop-set');
|
|
||||||
assert.equal(JSON.stringify(sandbox.transportEvents[0][1].loop), JSON.stringify({ startTime: 5, endTime: 10, enabled: true, state: 'active' }));
|
|
||||||
assert.equal(sandbox.transportEvents[1][0], 'loop-cleared');
|
|
||||||
assert.equal(JSON.stringify(sandbox.transportEvents[1][1].loop), JSON.stringify({ enabled: false, state: 'inactive' }));
|
|
||||||
|
|
||||||
sandbox.transportEvents.length = 0;
|
|
||||||
await sandbox.__setLoop(7, 11, { emitTransportEvent: false });
|
|
||||||
sandbox.__clearLoop({ emitTransportEvent: false });
|
|
||||||
assert.equal(sandbox.transportEvents.length, 0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('window.slopsmith API surface declares setLoop/clearLoop/getLoop', () => {
|
test('window.slopsmith API surface declares setLoop/clearLoop/getLoop', () => {
|
||||||
@@ -242,9 +188,14 @@ test('window.slopsmith API surface declares setLoop/clearLoop/getLoop', () => {
|
|||||||
// renaming silently.
|
// renaming silently.
|
||||||
const src = fs.readFileSync(APP_JS, 'utf8');
|
const src = fs.readFileSync(APP_JS, 'utf8');
|
||||||
// Find the slopsmith Object.assign block and check method presence.
|
// Find the slopsmith Object.assign block and check method presence.
|
||||||
const m = src.match(/window\.slopsmith\s*=\s*Object\.assign\(_slopsmithBus,\s*\{([\s\S]*?)\}\);\s*if \(_slopsmithExisting/);
|
// Tolerates an optional `/** @type {...} */ (` JSDoc cast prefix on the
|
||||||
assert.ok(m, 'slopsmith Object.assign block not found');
|
// assignment (added when app.js opted into `// @ts-check`).
|
||||||
const block = m[1];
|
const startMatch = src.match(
|
||||||
|
/window\.slopsmith\s*=\s*(?:\/\*\*[\s\S]*?\*\/\s*\(\s*)?Object\.assign\((?:new EventTarget\(\)|_\w+),\s*\{/);
|
||||||
|
assert.ok(startMatch, 'slopsmith Object.assign block not found');
|
||||||
|
// The setLoop/clearLoop/getLoop methods live within the literal that
|
||||||
|
// follows; a bounded slice is robust against `})`-containing bodies.
|
||||||
|
const block = src.slice(startMatch.index, startMatch.index + 2000);
|
||||||
assert.match(block, /setLoop\s*\(/, 'setLoop method missing from slopsmith API');
|
assert.match(block, /setLoop\s*\(/, 'setLoop method missing from slopsmith API');
|
||||||
assert.match(block, /clearLoop\s*\(/, 'clearLoop method missing from slopsmith API');
|
assert.match(block, /clearLoop\s*\(/, 'clearLoop method missing from slopsmith API');
|
||||||
assert.match(block, /getLoop\s*\(/, 'getLoop method missing from slopsmith API');
|
assert.match(block, /getLoop\s*\(/, 'getLoop method missing from slopsmith API');
|
||||||
|
|||||||
Reference in New Issue
Block a user