diff --git a/CHANGELOG.md b/CHANGELOG.md index 44ce2aff3..2c70d9889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md ## [Unreleased] +- Simplify POWER UP to one Google Maps entry. Keep the optional server key + available through environment configuration without a second setup row or + missing-key reminder. + - Separate terrain, traffic, FIRMS and GBFS middleware into focused provider modules, preserving local configuration, routes and cache/error behavior. diff --git a/SECURITY.md b/SECURITY.md index 875071d34..d19a5446d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -51,7 +51,8 @@ Terminal development uses one ignored repository-root `.env` for both `GOOGLE_MAPS_API_KEY` (browser) and `GOOGLE_MAPS_SERVER_API_KEY` (server). The tracked `.env.example` documents both without credentials. Vite injects only the browser key; sharing an environment file does not expose the server -key. Both entries are available in Provider Settings. Pinokio saves them in +key. Provider Settings presents only the browser key as Google Maps; configure +the optional server key manually in the environment file. Pinokio uses its ignored `pinokio/ENVIRONMENT` instead, with app values and blanks taking precedence over inherited global values. An absent server key retains the browser-key fallback for existing single-key setups. diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 250a2f106..52a019edf 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -75,9 +75,11 @@ See [component ownership and adoption](CODE-BOUNDARIES.md). Local Places nearby/text search and the CCTV Street View fallback prefer `GOOGLE_MAPS_SERVER_API_KEY`, falling back to `GOOGLE_MAPS_API_KEY` when the server key is blank or absent. Only the browser key is injected into client -code. Both are optional and configured in the same ignored root `.env`, or -Pinokio's ignored `pinokio/ENVIRONMENT`, through Provider Settings or manual -editing. `.env.example` and `pinokio/_ENVIRONMENT` document the two entries. +code. POWER UP presents one Google Maps entry for the browser key. The optional +server key is configured manually in the same ignored root `.env`, or Pinokio's +ignored `pinokio/ENVIRONMENT`; it is omitted from Provider Settings and its +missing-key count. Existing server keys and the single-key fallback remain +supported. `.env.example` and `pinokio/_ENVIRONMENT` document both entries. The Street View headings tool uses the same server-first selection after resolving environment overrides per variable; its explicit `--key` wins. diff --git a/src/keySetupCore.mjs b/src/keySetupCore.mjs index 33e200a69..302272725 100644 --- a/src/keySetupCore.mjs +++ b/src/keySetupCore.mjs @@ -21,16 +21,17 @@ export const KEY_SETUP_UPDATE_LIMIT = 16; export const KEY_SETUP_APPEND_HEADER = '# Keys added by the in-app POWER UP panel'; /** - * Every key the panel offers, in the order it offers them — most magic per + * Provider credentials, in display order — most magic per * minute first. `tier` mirrors the README's color legend: 'metered' (🔴) is a * billing-enabled account, 'free' (🟡) is a register-and-paste key. * `clientExposed` marks the two keys that are injected into the browser * bundle by design (restrict them at the provider, per SECURITY.md). + * `hidden` keeps advanced configuration out of the panel and missing-key count. */ export const KEY_SETUP_KEYS = Object.freeze([ Object.freeze({ id: 'google-maps', - title: 'GOOGLE MAPS — BROWSER', + title: 'GOOGLE MAPS', unlocks: 'The photorealistic 3D planet + place search', getUrl: 'https://developers.google.com/maps/documentation/tile/get-api-key', envVars: Object.freeze(['GOOGLE_MAPS_API_KEY']), @@ -44,6 +45,7 @@ export const KEY_SETUP_KEYS = Object.freeze([ getUrl: 'https://developers.google.com/maps/documentation/places/web-service/get-api-key', envVars: Object.freeze(['GOOGLE_MAPS_SERVER_API_KEY']), tier: 'metered', + hidden: true, }), Object.freeze({ id: 'openai', @@ -274,7 +276,7 @@ export function knownKeySetupEnvVars() { /** Tooltip guidance for a control gated by one registry entry. */ export function keySetupRequirement(id) { const entry = KEY_SETUP_KEYS.find((candidate) => candidate.id === id); - if (!entry) return ''; + if (!entry || entry.hidden) return ''; return `Needs ${entry.envVars.join(' + ')} — add it in Provider Settings`; } @@ -303,7 +305,7 @@ export function isKeySetupExternallyManaged({ * @param {Record} env e.g. process.env */ export function keySetupStatus(env = {}) { - const keys = KEY_SETUP_KEYS.map((entry) => { + const keys = KEY_SETUP_KEYS.filter((entry) => !entry.hidden).map((entry) => { const values = entry.envVars.map((name) => String(env[name] ?? '').trim()); const set = values.every((value) => value.length > 0); return { diff --git a/src/keySetupCore.test.mjs b/src/keySetupCore.test.mjs index 24ade8cd9..6170b43f2 100644 --- a/src/keySetupCore.test.mjs +++ b/src/keySetupCore.test.mjs @@ -63,7 +63,7 @@ test('the status payload reports presence without any credential material', () = // Secret missing: the OpenSky pair must read as NOT set. }; const status = keySetupStatus(env); - assert.equal(status.total, KEY_SETUP_KEYS.length); + assert.equal(status.total, KEY_SETUP_KEYS.filter((key) => !key.hidden).length); const google = status.keys.find((key) => key.id === 'google-maps'); assert.equal(google.set, true); const opensky = status.keys.find((key) => key.id === 'opensky'); @@ -342,15 +342,23 @@ test('validation rejects dotenv metacharacters that would round-trip wrong', () } }); -test('server Google key can be saved and removed without appearing in status values', () => { +test('server Google key remains supported without appearing in setup or its missing count', () => { const secret = 'server-key-fixture'; assert.deepEqual(validateKeySetupUpdates({ GOOGLE_MAPS_SERVER_API_KEY: secret }), { ok: true, updates: { GOOGLE_MAPS_SERVER_API_KEY: secret }, }); assert.equal(validateKeySetupUpdates({ GOOGLE_MAPS_SERVER_API_KEY: null }).ok, true); const status = keySetupStatus({ GOOGLE_MAPS_SERVER_API_KEY: secret }); - const entry = status.keys.find((key) => key.id === 'google-maps-server'); - assert.equal(entry.set, true); - assert.ok(!entry.clientExposed); + assert.deepEqual(status, keySetupStatus({})); + assert.equal(status.keys.some((key) => key.id === 'google-maps-server'), false); + assert.equal(keySetupRequirement('google-maps-server'), ''); + assert.equal(status.keys.find((key) => key.id === 'google-maps').title, 'GOOGLE MAPS'); + const allVisibleConfigured = Object.fromEntries(status.keys.flatMap((key) => + key.envVars.map((name) => [name, 'configured-fixture']), + )); + const complete = keySetupStatus(allVisibleConfigured); + assert.equal(complete.setCount, complete.total, 'an absent server key must not leave setup incomplete'); + assert.deepEqual(complete, keySetupStatus({ ...allVisibleConfigured, GOOGLE_MAPS_SERVER_API_KEY: secret })); + assert.ok(!JSON.stringify(status).includes('GOOGLE_MAPS_SERVER_API_KEY')); assert.ok(!JSON.stringify(status).includes(secret)); });