Skip to content

[UX Redesign] Reorganize Configurações into clear operational sections #19

Description

@luandro

Summary

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:
    1. Operação: operating_phase select, flight_providers checkboxes (adsb_lol / airplanes_live / adsbexchange), legacy-provider warning, poll_interval_seconds.
    2. Detecção: thresholds.
    3. Notificações: email provider / recipients / sender / SMTP settings + secrets + provider tests.
  • 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:
  • 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:

function openSettingsSection(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.
  • The empty #fr24 and #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.
  • Operação also absorbs #sync-now, #poll-now, #test-email actions 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).
  • 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.
  • Add i18n keys: 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-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:

  1. 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.
  2. Verify the new Configurações buttons work end-to-end (sync boundary, run poll, send test email) with the existing backend endpoints.
  3. 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.
  • Existing button-label translations (e.g. sync_boundaries, run_poll, test_email) carry over.
  • No new action translation keys are added unless a genuinely new string is required.

Do NOT hard-code Portuguese strings in JS. All copy goes through t(...).

Out of scope

Detailed behavior

Section list

Operação (this issue, fully populated)

Detecção / Notificações / Sistema

  • Detecção: all threshold inputs from #settings-thresholds carry over.
  • Notificações: email provider, alert_recipients, email_from, smtp_host / port / username / password (password placeholder "Deixe em branco para manter existente"), smtp_starttls; API keys (flightradar24_api_key, adsbexchange_api_key, resend_api_key).
  • Sistema: language select (pt / en), timezone select.

FR24 / Dados de voo (empty container this issue; populated by #18)

Registros (empty container this issue; populated by #20)

Locked settings

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.

Data / API impact

No backend/API changes expected.

Routing / navigation impact

  • #view-settings view ID stays (Issue [UX Redesign] Simplify global shell and primary navigation #13 keeps it stable).
  • 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".
  • Empty <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.
  • 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.
  • Regression: existing settings round-trip tests pass unchanged.
  • Accessibility: stepper is role="tablist", each step role="tab", locked badge has accessible label.
  • Responsive: screenshots at 1280, 768.

Visual QA

  • 1280px stepper collapsed showing the four populated steps (FR24 and Registros stepper buttons hidden).
  • 1280px stepper expanded on Operação, Detecção.
  • 1280px with an env-locked setting visible (badge).
  • 768px stacked sections.

Dependencies

Rollout / compatibility

  • No saved configuration changes.
  • No data migration.

Open questions

None.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions