feat(admin): the phone's API-keys card, rebuilt like the desktop tab

The desktop tab was reshaped around one idea: a key should be the exception
rather than the thing you are expected to paste. The phone still had the older
shape, where Google wore the "Recommended" chip and the TREK Places API was not
on the screen at all. Two pages, opposite advice, same setting.

So the phone gets the same three comparable blocks in the same order: the TREK
index first, with its own ribbon and a folded panel of what it does and does not
carry, then the two keys under theirs. The Google caveat is said before the
field rather than after it, because somebody about to paste a key should read it
while deciding. The four Google switches moved under the key they spend, folded
away: they are set once and never touched again, and open they were longer than
the rest of the card together. Open-Meteo left the card entirely, as it did on
the desktop, since it takes no key and had the loudest treatment of anything
there.

The provider choice is TREK's own picker now. A native select is drawn by the
operating system and was the one control on the page that did not belong to the
app.

Its description was also describing a TREK that no longer exists: it said one
service answers place search. The index and OpenStreetMap answer every search
and the setting only decides who is asked once they come up empty
(maps.service.ts, keyedProvider). Both that line and the missing-key notice say
so now, in all 23 languages, and the two key hints have short forms for the
narrow screen.
This commit is contained in:
Maurice
2026-09-13 21:07:55 +02:00
parent c2751d9c80
commit 76f45512d1
28 changed files with 448 additions and 159 deletions
@@ -274,7 +274,8 @@ describe('MAdminSettingsSection', () => {
expect(screen.queryByLabelText('Google Maps API Key')).not.toBeInTheDocument();
expect(screen.queryByLabelText(/Amap/)).not.toBeInTheDocument();
expect(screen.queryByLabelText('Unsplash API Key')).not.toBeInTheDocument();
expect(screen.getByDisplayValue('Automatic')).toBeInTheDocument();
// The native select became TREK's own picker: a button carrying the choice.
expect(screen.getByRole('button', { name: 'Automatic' })).toBeInTheDocument();
});
it('FE-MOB-ASET-016c: the missing-key notice follows app-config, not the fields', () => {
@@ -282,11 +283,11 @@ describe('MAdminSettingsSection', () => {
// failing. Which of the two is true is app-config's answer: the key fields
// are empty on a managed install and on a key set by the environment.
const { unmount } = render(<Harness admin={buildAdminHook({ placesProvider: 'amap', hasAmapKey: false })} />);
expect(screen.getByText(/falls back to OpenStreetMap/i)).toBeInTheDocument();
expect(screen.getByText(/TREK index and OpenStreetMap alone/i)).toBeInTheDocument();
unmount();
renderSettings({ managed: true, placesProvider: 'google', mapsKey: '', hasMapsKey: true });
expect(screen.queryByText(/falls back to OpenStreetMap/i)).not.toBeInTheDocument();
expect(screen.queryByText(/TREK index and OpenStreetMap alone/i)).not.toBeInTheDocument();
});
it('FE-MOB-ASET-017: the Google Places toggles persist optimistically', async () => {
@@ -307,6 +308,9 @@ describe('MAdminSettingsSection', () => {
}),
);
const admin = renderSettings();
// They live under the Google key now, folded away by default: set once when a
// key is pasted, never touched again.
await user.click(screen.getByRole('button', { name: 'What the key is used for' }));
await user.click(toggle('Place Photos'));
await user.click(toggle('Place Autocomplete'));
@@ -331,6 +335,7 @@ describe('MAdminSettingsSection', () => {
placesAutocompleteEnabled: true,
placesDetailsEnabled: true,
});
await user.click(screen.getByRole('button', { name: 'What the key is used for' }));
await user.click(toggle('Place Photos'));
await user.click(toggle('Place Autocomplete'));
@@ -342,13 +347,14 @@ describe('MAdminSettingsSection', () => {
expect(admin.setPlacesDetailsEnabledState).toHaveBeenLastCalledWith(true);
});
it('FE-MOB-ASET-019: the Open-Meteo card lists the three facts', () => {
it('FE-MOB-ASET-019: the API-keys card is about keys, and weather needs none', () => {
// The Open-Meteo panel left this card, as it did on the desktop tab: it takes
// no key and has nothing to configure, so it had no business in a card about
// keys, where it had the loudest treatment of anything on it.
renderSettings();
expect(screen.getByText('Weather Data')).toBeInTheDocument();
expect(screen.getByText('Free, no API key required')).toBeInTheDocument();
expect(screen.getByText('16-day forecast')).toBeInTheDocument();
expect(screen.getByText('10,000 requests / day')).toBeInTheDocument();
expect(screen.queryByText('Weather Data')).not.toBeInTheDocument();
expect(screen.queryByText('10,000 requests / day')).not.toBeInTheDocument();
});
it('FE-MOB-ASET-020: the OIDC form renders its values and saves a payload without an empty secret', async () => {
@@ -131,7 +131,9 @@ describe('MJourneyEntrySheet quick capture', () => {
// move arrived nameless and the day read as a column of placeholders
// (discussion #2299). The story field is what Add details still unlocks.
expect(screen.getByPlaceholderText('Give this moment a name...')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Add details' }));
// Shortened to "+ Details": the old label wrapped onto two lines in the
// sheet's footer beside Cancel and Save (discussion #2299).
fireEvent.click(screen.getByRole('button', { name: '+ Details' }));
expect(screen.getByPlaceholderText('Give this moment a name...')).toBeInTheDocument();
expect(screen.getByPlaceholderText('Write your story...')).toBeInTheDocument();
await waitFor(() => expect(mapsApi.reverse).toHaveBeenCalled());