You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Create the Configurações skeleton (section list, anchor IDs, locked-settings badge pattern) inside the existing #view-settings view, preserving every existing setting and validation behavior. Operação also absorbs the manual sync / manual poll / test-email actions previously on Monitoramento (Issue #14). Children #18 / #20 / #21 then migrate the FR24, Logs and Phase sub-surfaces into the new structure without changing their semantics. Settings contract: setting_source() returns one of "environment", "interface", "default", and /api/settings returns locked (not is_locked).
Current state
#view-settings (app/static/index.html:97-146) is a 3-step stepper:
Manual sync (#sync-now), manual poll (#poll-now) and test-email (#test-email) currently live on the Monitoramento panel (app/static/index.html:62-66). Their handlers, in app/static/app.js:1606-1608, call runAction(button, text, endpoint) directly with:
/api/boundaries/sync (for #sync-now)
/api/poll (for #poll-now)
/api/email/test (for #test-email) There are no runBoundarySync() / manualPoll() / sendTestEmail() named helpers in the codebase. Issue [UX Redesign] Recompose dashboard as map-first Monitoramento workspace #14 keeps these handlers unchanged on Monitoramento; this issue moves them here atomically.
Save handlers: app/static/app.js has saveForm() + form-specific submit handlers.
SETTING_DEFS (app/settings_store.py) is the source of truth for every setting key, env var, default, parser, secret flag, and source tracking.
setting_source(key) (app/settings_store.py:252) returns one of "environment", "interface", "default". NOT"database".
/api/settings returns each setting as {value, source: "environment" | "interface" | "default", locked: bool, secret: bool, choices, kind}. The response uses locked (not is_locked) and the source strings above.
Settings stepper uses ARIA tabs ('role="tab"' with data-step) and #settings-step-N panels. initSettingsStepper() (app/static/app.js:1435) defines a private nested showStep(n) closure at line 1440 that updates ARIA / active class / hidden on the panels. Today this is the only thing that calls showStep. A future openSettingsSection() cannot simply reach into the closure — see Settings-step activation architecture below.
UX target
Same #view-settings content (view ID stays — Issue #13 keeps it stable) with a vertical section list (extending the existing stepper pattern). Up to six sections, one per concern:
Configurações
└─ Operação ← phase, providers, poll interval, manual sync, manual poll, test email
└─ Detecção ← thresholds
└─ Notificações ← email
└─ FR24 / Dados de voo ← power toggle, budget policy, status, retention, overlap, test (Issue #18)
└─ Dados / Sistema ← language, timezone (display)
└─ Registros ← logs (Issue #20)
Each section is a <section> with id="operacao", id="deteccao", id="notificacoes", id="fr24", id="sistema", id="registros". Section focus / scroll into view uses the new tiny DOM helper openSettingsSection(sectionId) introduced next to the tab-activation code in app/static/app.js.
This issue creates the skeleton plus populated sections for Operação (including the manual actions moved from Monitoramento), Detecção, Notificações, and Sistema. Children #18 / #20 / #21 populate FR24, Registros, and the phase-change UX respectively.
Stepper UI — hide unavailable sections
Until their owning child issues land, the FR24 and Registros step buttons are hidden in the Settings stepper rather than shown as empty/blank destinations. Do NOT display GitHub-issue-referencing placeholder copy. Implementation: render the step buttons for #operacao, #deteccao, #notificacoes, #sistema always; render the #fr24 step button only after #18 lands; render the #registros step button only after #20 lands.
Empty section containers — both must exist
The empty <section id="fr24"> and <section id="registros"> containers MUST exist in #view-settings after this issue lands. Their anchor IDs (#fr24, #registros) are required so that openSettingsSection("fr24") and openSettingsSection("registros") resolve when called (e.g. by Monitoramento after #18 lands). Their stepper buttons are hidden until populated. No body content is required from this issue — the anchors and (empty) containers are the deliverable for the skeleton.
Existing functionality to reuse
All five forms (#settings-core, #settings-display, #settings-thresholds, #settings-email, #settings-keys).
All submit handlers and saveForm() helper.
The manual action handlers runAction(button, text, endpoint) (app/static/app.js:1477) — wired to #sync-now, #poll-now, #test-email click handlers (currently lines 1606-1608). This issue rebinds them to the Operação buttons in their new Configurações home and removes them from Monitoramento (see Atomic move below).
setting_source() and the locked boolean already returned by /api/settings.
SETTING_DEFS — every key + metadata is the contract.
public_settings() returns {value, source, locked, secret, choices, kind} per key.
initSettingsStepper() and its internal showStep(n) closure (app/static/app.js:1440) — see Settings-step activation architecture below.
i18n keys: every existing settings_* translation in app/i18n.py.
Settings-step activation architecture (explicit)
The current showStep(n) is a private closure inside initSettingsStepper(). openSettingsSection() cannot simply reach into that closure. This issue resolves this by a small refactor — there is exactly one source of truth for stepper state.
Concretely:
Extract the existing nested showStep(n) body into a shared top-level helper, e.g. showSettingsStep(n). The helper updates ARIA / active class / hidden on the step tabs and panels exactly the same way the nested closure does today.
initSettingsStepper() calls showSettingsStep(n) from its existing click / prev / next handlers. The stepper's existing behavior is unchanged.
openSettingsSection(sectionId) (introduced by this issue, see below) maps the section id to its step number and calls the same showSettingsStep(n).
Therefore ARIA selection, hidden state and active classes have one source of truth. No duplication.
Cross-section navigation helper
This issue introduces openSettingsSection(sectionId) as a tiny DOM helper next to the tab-activation code in app/static/app.js:
functionopenSettingsSection(sectionId){// 1. Activate the Settings primary tab (existing tab activation path).// 2. Map sectionId → step number and call the shared showSettingsStep(n).// This updates ARIA / visible state on the step tabs and panels.// 3. Optionally trigger a section-specific loader (e.g. loadFr24(),// loadLogs()) if the section needs it; children #18 / #20 attach// such loaders when they land.// 4. Focus + scrollIntoView the matching <section id="{sectionId}">.}
The helper is called from Monitoramento's "Detalhes em Configurações › FR24" link (after #18 rewires it), from any other cross-section jump, and from anywhere else in the operator UI that needs to focus a Settings section. It is not a hash router.
Scope
Refactor initSettingsStepper() to extract showStep into a shared top-level showSettingsStep(n).
Add openSettingsSection(sectionId) next to the tab-activation code.
Reorganize the existing 3-step stepper into up to six sections. Operação, Detecção, Notificações, Sistema are populated by this issue.
For settings that are environment-locked (source === "environment" and locked === true), surface a small badge next to the input: "Definido por variável de ambiente". Reuse the visual pattern of #fr24-policy-env-badge.
Secrets: never redisplay the saved value. Existing behavior: type="password" with placeholder "Deixe em branco para manter existente". Reuse verbatim.
Atomic move of manual actions (operational responsibility)
Issue #14 does NOT remove #sync-now, #poll-now, #test-email from Monitoramento. This issue owns the atomic move. The single PR that lands this issue must perform all three steps together, so no merged state leaves the actions unreachable:
Add#sync-now, #poll-now, #test-email buttons (and their #action-result container, if needed) under Configurações › Operação. Rebind them to runAction(button, text, endpoint) with /api/boundaries/sync, /api/poll, /api/email/test respectively — the same handlers that exist on Monitoramento today. This may either rebind the existing handlers in place or extract tiny named frontend helpers (e.g. manualSync(), manualPoll(), testEmail()) if useful; either way, the helpers do not exist yet and are introduced here.
Verify the new Configurações buttons work end-to-end (sync boundary, run poll, send test email) with the existing backend endpoints.
Remove the corresponding buttons from Monitoramento's "Operações" panel. Do not leave a merged state where Monitoramento's buttons no longer work or where the actions are unreachable.
Until this issue merges, Monitoramento's buttons remain in place and continue to work.
Localization for the moved buttons
Reuse the EXISTING localization keys and calls:
Button progress / status copy through existing t(...) keys action_syncing, action_polling, action_testing_email.
For each setting where the API returns source === "environment" and locked === true, the input becomes readonly and a small badge appears next to it: "Definido por variável de ambiente". Reuse the visual pattern of #fr24-policy-env-badge. Implementation must read the field name locked (not is_locked) and the source string "environment" (not "env"). Note that setting_source() returns "environment" | "interface" | "default" — not "database".
Section navigation
"Anterior" / "Próximo" buttons at the bottom of each step (existing pattern).
Keyboard: native browser form order.
Save behavior
One save button per form. No auto-save.
After save, form-result div shows success or per-field errors.
Loading / error state
Stepper switcher: nothing async; pure DOM toggle.
Save in flight: saveForm() already shows a loading state via withLoading().
Responsive behavior
Stepper adapts to the existing responsive grid. Forms keep their existing form-grid and form-grid compact classes.
Keyboard behavior
Native form keyboard order. Stepper prev / next buttons reachable via Tab.
Anchors #operacao, #deteccao, #notificacoes, #fr24, #sistema, #registros allow deep focusing via openSettingsSection(sectionId).
There is no #/settings URL route. Today the frontend is DOM-driven; activating the Settings tab happens via tab.dataset.view. This issue does not introduce a route and does not advertise one.
No new hash routes. No general hash router.
Security / privacy
All secrets remain type="password" with the "leave blank to keep" placeholder.
No new endpoints, no new permissions.
Acceptance criteria
Every existing setting remains reachable from one of the six sections.
No setting changes meaning, default, env var, or parser.
Environment-locked settings show a "definido por variável de ambiente" badge, computed from source === "environment" && locked === true on the existing API response.
Secrets never redisplay; placeholder remains "Deixe em branco para manter existente".
Operação / Detecção / Notificações / Sistema sections work end-to-end with no behavior change vs. today (Operação additionally absorbs the manual sync / manual poll / test-email actions previously on Monitoramento).
Atomic move verified: in the single PR that lands this issue, #sync-now, #poll-now, #test-email are added to Operação AND removed from Monitoramento together, AND the new buttons work end-to-end. Reuse existing t(...) keys (action_syncing, action_polling, action_testing_email) and existing button-label translations; no new action translation keys unless a genuinely new string is needed; no hard-coded Portuguese strings in JS.
The Settings-step activation logic is shared via an externally-callable showSettingsStep(n) (extracted from the private showStep closure inside initSettingsStepper()). initSettingsStepper() uses showSettingsStep(n) for its click / prev / next handlers. openSettingsSection(sectionId) calls the same showSettingsStep(n). ARIA selection, hidden state and active classes have one source of truth.
openSettingsSection(sectionId) is exported / available globally so other tabs can call it.
No backend changes.
Implementation agents read locked and source: "environment" (or source: "interface", source: "default") from the existing API — NOT is_locked / env / database.
No named helper function in the codebase is referenced as if it already existed (runBoundarySync() / manualPoll() / sendTestEmail() do not exist; if introduced, they are introduced here).
No #/settings URL is described as a route.
Test plan
VM JS (tests/test_configuracoes_vm.mjs): stepper navigation between the four populated steps; assertion that step activation goes through showSettingsStep(n) and no duplicate state-update logic exists; locked-badge presence on env-controlled settings; secret inputs not populated on reload; FR24 / Registros stepper buttons hidden until children land; openSettingsSection("fr24") activates the Settings tab and focuses the FR24 section; openSettingsSection uses the shared showSettingsStep(n).
Integration: save each populated section's form, reload, assert persistence; trigger manual sync / manual poll / test email from Operação and verify behaviour matches the previous Monitoramento behavior.
Summary
Create the Configurações skeleton (section list, anchor IDs, locked-settings badge pattern) inside the existing
#view-settingsview, preserving every existing setting and validation behavior. Operação also absorbs the manual sync / manual poll / test-email actions previously on Monitoramento (Issue #14). Children #18 / #20 / #21 then migrate the FR24, Logs and Phase sub-surfaces into the new structure without changing their semantics. Settings contract:setting_source()returns one of"environment","interface","default", and/api/settingsreturnslocked(notis_locked).Current state
#view-settings(app/static/index.html:97-146) is a 3-step stepper:#sync-now), manual poll (#poll-now) and test-email (#test-email) currently live on the Monitoramento panel (app/static/index.html:62-66). Their handlers, inapp/static/app.js:1606-1608, callrunAction(button, text, endpoint)directly with:/api/boundaries/sync(for#sync-now)/api/poll(for#poll-now)/api/email/test(for#test-email)There are no
runBoundarySync()/manualPoll()/sendTestEmail()named helpers in the codebase. Issue [UX Redesign] Recompose dashboard as map-first Monitoramento workspace #14 keeps these handlers unchanged on Monitoramento; this issue moves them here atomically.app/static/app.jshassaveForm()+ form-specific submit handlers.SETTING_DEFS(app/settings_store.py) is the source of truth for every setting key, env var, default, parser, secret flag, and source tracking.setting_source(key)(app/settings_store.py:252) returns one of"environment","interface","default". NOT"database"./api/settingsreturns each setting as{value, source: "environment" | "interface" | "default", locked: bool, secret: bool, choices, kind}. The response useslocked(notis_locked) and the source strings above.'role="tab"'withdata-step) and#settings-step-Npanels.initSettingsStepper()(app/static/app.js:1435) defines a private nestedshowStep(n)closure at line 1440 that updates ARIA /activeclass /hiddenon the panels. Today this is the only thing that callsshowStep. A futureopenSettingsSection()cannot simply reach into the closure — see Settings-step activation architecture below.UX target
Same
#view-settingscontent (view ID stays — Issue #13 keeps it stable) with a vertical section list (extending the existing stepper pattern). Up to six sections, one per concern:Each section is a
<section>withid="operacao",id="deteccao",id="notificacoes",id="fr24",id="sistema",id="registros". Section focus / scroll into view uses the new tiny DOM helperopenSettingsSection(sectionId)introduced next to the tab-activation code inapp/static/app.js.This issue creates the skeleton plus populated sections for Operação (including the manual actions moved from Monitoramento), Detecção, Notificações, and Sistema. Children #18 / #20 / #21 populate FR24, Registros, and the phase-change UX respectively.
Stepper UI — hide unavailable sections
Until their owning child issues land, the FR24 and Registros step buttons are hidden in the Settings stepper rather than shown as empty/blank destinations. Do NOT display GitHub-issue-referencing placeholder copy. Implementation: render the step buttons for
#operacao,#deteccao,#notificacoes,#sistemaalways; render the#fr24step button only after #18 lands; render the#registrosstep button only after #20 lands.Empty section containers — both must exist
The empty
<section id="fr24">and<section id="registros">containers MUST exist in#view-settingsafter this issue lands. Their anchor IDs (#fr24,#registros) are required so thatopenSettingsSection("fr24")andopenSettingsSection("registros")resolve when called (e.g. by Monitoramento after #18 lands). Their stepper buttons are hidden until populated. No body content is required from this issue — the anchors and (empty) containers are the deliverable for the skeleton.Existing functionality to reuse
#settings-core,#settings-display,#settings-thresholds,#settings-email,#settings-keys).saveForm()helper.runAction(button, text, endpoint)(app/static/app.js:1477) — wired to#sync-now,#poll-now,#test-emailclick handlers (currently lines 1606-1608). This issue rebinds them to the Operação buttons in their new Configurações home and removes them from Monitoramento (see Atomic move below).setting_source()and thelockedboolean already returned by/api/settings.SETTING_DEFS— every key + metadata is the contract.public_settings()returns{value, source, locked, secret, choices, kind}per key.initSettingsStepper()and its internalshowStep(n)closure (app/static/app.js:1440) — see Settings-step activation architecture below.settings_*translation inapp/i18n.py.Settings-step activation architecture (explicit)
The current
showStep(n)is a private closure insideinitSettingsStepper().openSettingsSection()cannot simply reach into that closure. This issue resolves this by a small refactor — there is exactly one source of truth for stepper state.Concretely:
showStep(n)body into a shared top-level helper, e.g.showSettingsStep(n). The helper updates ARIA /activeclass /hiddenon the step tabs and panels exactly the same way the nested closure does today.initSettingsStepper()callsshowSettingsStep(n)from its existing click / prev / next handlers. The stepper's existing behavior is unchanged.openSettingsSection(sectionId)(introduced by this issue, see below) maps the section id to its step number and calls the sameshowSettingsStep(n).Cross-section navigation helper
This issue introduces
openSettingsSection(sectionId)as a tiny DOM helper next to the tab-activation code inapp/static/app.js:The helper is called from Monitoramento's "Detalhes em Configurações › FR24" link (after #18 rewires it), from any other cross-section jump, and from anywhere else in the operator UI that needs to focus a Settings section. It is not a hash router.
Scope
initSettingsStepper()to extractshowStepinto a shared top-levelshowSettingsStep(n).openSettingsSection(sectionId)next to the tab-activation code.#fr24and#registros<section>containers exist after this issue lands (anchors for [UX Redesign] Move FR24 usage and budget into Monitoramento and Configurações #18 / [UX Redesign] Move Registros into Configurações without changing audit behavior #20). The FR24 and Registros stepper buttons are hidden until their owning child issues land.#sync-now,#poll-now,#test-emailactions moved from Monitoramento (Issue [UX Redesign] Recompose dashboard as map-first Monitoramento workspace #14 keeps the handlers but does NOT remove the Monitoramento buttons; this issue owns the atomic move).source === "environment"andlocked === true), surface a small badge next to the input: "Definido por variável de ambiente". Reuse the visual pattern of#fr24-policy-env-badge.type="password"with placeholder "Deixe em branco para manter existente". Reuse verbatim.settings_section_operation,settings_section_detection,settings_section_notifications,settings_section_fr24,settings_section_system,settings_section_logs,settings_locked_env.Atomic move of manual actions (operational responsibility)
Issue #14 does NOT remove
#sync-now,#poll-now,#test-emailfrom Monitoramento. This issue owns the atomic move. The single PR that lands this issue must perform all three steps together, so no merged state leaves the actions unreachable:#sync-now,#poll-now,#test-emailbuttons (and their#action-resultcontainer, if needed) under Configurações › Operação. Rebind them torunAction(button, text, endpoint)with/api/boundaries/sync,/api/poll,/api/email/testrespectively — the same handlers that exist on Monitoramento today. This may either rebind the existing handlers in place or extract tiny named frontend helpers (e.g.manualSync(),manualPoll(),testEmail()) if useful; either way, the helpers do not exist yet and are introduced here.Until this issue merges, Monitoramento's buttons remain in place and continue to work.
Localization for the moved buttons
Reuse the EXISTING localization keys and calls:
t(...)keysaction_syncing,action_polling,action_testing_email.sync_boundaries,run_poll,test_email) carry over.Do NOT hard-code Portuguese strings in JS. All copy goes through
t(...).Out of scope
SETTING_DEFSalready enforces.#/settingsURL was ever a route.Detailed behavior
Section list
Operação (this issue, fully populated)
#sync-now(callsrunAction(button, t("action_syncing"), "/api/boundaries/sync")),#poll-now(callsrunAction(button, t("action_polling"), "/api/poll")),#test-email(callsrunAction(button, t("action_testing_email"), "/api/email/test")). Each preserves the existing success / failure copy pattern (#action-resultor equivalent).Detecção / Notificações / Sistema
#settings-thresholdscarry over.FR24 / Dados de voo (empty container this issue; populated by #18)
<section id="fr24">anchor exists. The stepper button is hidden until [UX Redesign] Move FR24 usage and budget into Monitoramento and Configurações #18 lands. No GitHub-issue-referencing copy is rendered.Registros (empty container this issue; populated by #20)
<section id="registros">anchor exists. The stepper button is hidden until [UX Redesign] Move Registros into Configurações without changing audit behavior #20 lands. No GitHub-issue-referencing copy is rendered.Locked settings
For each setting where the API returns
source === "environment"andlocked === true, the input becomes readonly and a small badge appears next to it: "Definido por variável de ambiente". Reuse the visual pattern of#fr24-policy-env-badge. Implementation must read the field namelocked(notis_locked) and the source string"environment"(not"env"). Note thatsetting_source()returns"environment" | "interface" | "default"— not"database".Section navigation
Save behavior
form-resultdiv shows success or per-field errors.Loading / error state
saveForm()already shows a loading state viawithLoading().Responsive behavior
Stepper adapts to the existing responsive grid. Forms keep their existing
form-gridandform-grid compactclasses.Keyboard behavior
Native form keyboard order. Stepper prev / next buttons reachable via Tab.
Data / API impact
No backend/API changes expected.
Routing / navigation impact
#view-settingsview ID stays (Issue [UX Redesign] Simplify global shell and primary navigation #13 keeps it stable).#operacao,#deteccao,#notificacoes,#fr24,#sistema,#registrosallow deep focusing viaopenSettingsSection(sectionId).#/settingsURL route. Today the frontend is DOM-driven; activating the Settings tab happens viatab.dataset.view. This issue does not introduce a route and does not advertise one.Security / privacy
type="password"with the "leave blank to keep" placeholder.Acceptance criteria
source === "environment" && locked === trueon the existing API response.<section id="fr24">and<section id="registros">containers exist after this issue lands. FR24 and Registros stepper buttons are hidden until [UX Redesign] Move FR24 usage and budget into Monitoramento and Configurações #18 / [UX Redesign] Move Registros into Configurações without changing audit behavior #20 land. No GitHub-issue-referencing copy in the operator UI at any point.#sync-now,#poll-now,#test-emailare added to Operação AND removed from Monitoramento together, AND the new buttons work end-to-end. Reuse existingt(...)keys (action_syncing,action_polling,action_testing_email) and existing button-label translations; no new action translation keys unless a genuinely new string is needed; no hard-coded Portuguese strings in JS.showSettingsStep(n)(extracted from the privateshowStepclosure insideinitSettingsStepper()).initSettingsStepper()usesshowSettingsStep(n)for its click / prev / next handlers.openSettingsSection(sectionId)calls the sameshowSettingsStep(n). ARIA selection, hidden state and active classes have one source of truth.openSettingsSection(sectionId)is exported / available globally so other tabs can call it.lockedandsource: "environment"(orsource: "interface",source: "default") from the existing API — NOTis_locked/env/database.runBoundarySync()/manualPoll()/sendTestEmail()do not exist; if introduced, they are introduced here).#/settingsURL is described as a route.Test plan
tests/test_configuracoes_vm.mjs): stepper navigation between the four populated steps; assertion that step activation goes throughshowSettingsStep(n)and no duplicate state-update logic exists; locked-badge presence on env-controlled settings; secret inputs not populated on reload; FR24 / Registros stepper buttons hidden until children land;openSettingsSection("fr24")activates the Settings tab and focuses the FR24 section;openSettingsSectionuses the sharedshowSettingsStep(n).role="tablist", each steprole="tab", locked badge has accessible label.Visual QA
Dependencies
Rollout / compatibility
Open questions
None.