feat(ui): render Provider Settings from a store

initKeySetup keeps the setup requests, the ChatGPT OAuth flow, the pasted
field values and the dialog's keyboard, and publishes the POWER UP chip,
the dialog, its key rows and the status line to the gev-provider-settings
store. ProviderSettingsView renders them and sends clicks and typing to
the connected owner; in Cyber the chip is teleported into the top
toolbar. A successful save renders the rows afresh so no field keeps a
key, and the status line announces a repeated message again. The voice
action opens and closes the dialog through the same controls.
This commit is contained in:
Sameh Khamis committed 2026-10-31 18:22:20 -07:00
1 parent 6cb01ff975
commit fe963a1219
9 files changed
+840 -517

No files matched your search

+17 -74
View File
@@ -4,61 +4,14 @@ import {
collectKeyUpdates,
keySetupChipLabel,
stripKeylessBasemapFromHash,
bindKeySetupPlacement,
waitForChatGptOAuth,
initKeySetup,
} from './keySetup.js';
import { createPinia } from 'pinia';
import { describeUnsavedWork } from './i18n/index.js';
import { useProviderSettingsStore } from './ui/stores/providerSettings.js';
test('setup placement moves the same button only in Cyber and disconnects on teardown', () => {
const home = {
prepend: (node) => {
node.parentNode = home;
},
};
const chip = { parentNode: home, hidden: true };
let moves = 0;
const toolbar = {
append: (node) => {
moves++;
node.parentNode = toolbar;
},
};
const theme = { dataset: { uiTheme: 'tactical' } };
let notify,
disconnected = false;
class Observer {
constructor(callback) {
notify = callback;
}
observe(target, options) {
assert.equal(target, theme);
assert.deepEqual(options.attributeFilter, ['data-ui-theme']);
}
disconnect() {
disconnected = true;
}
}
const dispose = bindKeySetupPlacement(
{
documentElement: theme,
getElementById: () => toolbar,
defaultView: { MutationObserver: Observer },
},
chip,
);
assert.equal(chip.parentNode, home);
for (const variant of ['cyber', 'tactical', 'cyber', 'minimal']) {
theme.dataset.uiTheme = variant;
notify();
assert.equal(chip.parentNode, variant === 'cyber' ? toolbar : home);
notify();
}
assert.equal(moves, 2, 'identical theme sync does not reparent again');
assert.equal(chip.hidden, true, 'placement cannot reveal a retired button');
dispose();
assert.equal(disconnected, true);
});
const freshStore = () => useProviderSettingsStore(createPinia());
test('the chip counts what is missing, and retires the count at zero', () => {
assert.equal(keySetupChipLabel({ setCount: 0, total: 8 }), 'POWER UP · 8 KEYS WAITING');
@@ -153,52 +106,42 @@ test('the first Google key strips ONLY the keyless OSM basemap from the share ha
});
test('aborting pending setup removes its surface and ignores a late response', async () => {
const { initKeySetup } = await import('./keySetup.js');
const removed = [];
const chip = { remove: () => removed.push('chip') };
const root = { dataset: {}, remove: () => removed.push('root') };
const store = freshStore();
const root = { dataset: {} };
let resolveResponse;
let requestSignal;
const controller = new AbortController();
const pending = initKeySetup({
documentRef: { getElementById: (id) => id === 'key-setup-chip' ? chip : root },
documentRef: { getElementById: (id) => (id === 'key-setup' ? root : null) },
signal: controller.signal,
store,
fetchImpl: (_url, { signal }) => {
requestSignal = signal;
return new Promise((resolve) => { resolveResponse = resolve; });
},
});
assert.equal(store.controls !== null, true);
controller.abort();
assert.equal(requestSignal.aborted, true);
assert.deepEqual(removed, ['chip', 'root']);
assert.equal(store.view.available, false);
assert.equal(store.controls, null);
resolveResponse({ ok: true, json: async () => ({ keys: [] }) });
assert.equal(await pending, null);
assert.equal(store.view.available, false);
});
test('a pasted, unsaved key makes the language switch ask first until teardown', async () => {
const node = () => ({
dataset: {},
addEventListener() {},
removeEventListener() {},
setAttribute() {},
querySelector: () => null,
remove() {},
});
const chip = node();
const input = { dataset: { envVar: 'GOOGLE_MAPS_API_KEY' }, value: '' };
const root = {
...node(),
querySelectorAll: (selector) =>
selector === 'input[data-env-var]' ? [input] : [],
};
const store = freshStore();
const root = { dataset: {} };
const controller = new AbortController();
const setup = await initKeySetup({
documentRef: {
getElementById: (id) => (id === 'key-setup-chip' ? chip : root),
getElementById: (id) => (id === 'key-setup' ? root : null),
addEventListener() {},
removeEventListener() {},
},
signal: controller.signal,
store,
fetchImpl: async () => ({
ok: true,
json: async () => ({ keys: [], setCount: 0, total: 1 }),
@@ -206,9 +149,9 @@ test('a pasted, unsaved key makes the language switch ask first until teardown',
});
assert.ok(setup);
assert.deepEqual(describeUnsavedWork(), []);
input.value = ' ';
store.controls.setField('GOOGLE_MAPS_API_KEY', ' ');
assert.deepEqual(describeUnsavedWork(), []);
input.value = 'AIza-pasted';
store.controls.setField('GOOGLE_MAPS_API_KEY', 'AIza-pasted');
assert.deepEqual(describeUnsavedWork(), ['unsaved provider keys']);
// The field still holds the key, so only teardown dropping the check clears it.
controller.abort();