Skip to content

GUI Config Editor Design

Goal

Add a graphical config editor to the sap-devs-tray system tray companion, accessible from the tray context menu and from the dashboard panel. This is Phase 3 of the Content Editing UI (TODO.md), scoped to config editing only.

Context

The TUI config editor (sap-devs config edit) already exists with 4 form groups and ~20 fields. The GUI version mirrors this functionality in a webview window using the same embedded HTTP server + Fiori-styled frontend approach as the existing dashboard panel.

Architecture

Window Behavior

A separate webview window (not embedded in the dashboard panel):

  • Size: ~520×700 pixels, resizable
  • Title bar: Standard OS window chrome (not frameless)
  • Close behavior: Standard close button, no hide-on-focus-loss
  • Opened from: "Config" item in tray context menu, or a link/button in the dashboard panel
  • URL: config.html?token=TOKEN served by the existing embedded HTTP server

Backend (Go)

New file cmd/sap-devs-tray/config.go with API handlers. All endpoints use the existing requireToken middleware.

Config read/write:

EndpointMethodPurpose
/api/configGETReturns current config as JSON
/api/configPOSTValidates and saves config; returns field-level errors or {status: "ok"}

Input assistance:

EndpointMethodPurpose
/api/cities?q=<prefix>GETReturns top 10 city matches from cities.json (case-insensitive prefix on city name)
/api/languagesGETReturns supported language codes (discovered from i18n catalogs)
/api/detect-locationPOSTCalls ip-api.com/json, returns {city, country} or error

Service & tray actions:

EndpointMethodPurpose
/api/service-statusGETReturns {scheduler: {installed, lastRun, nextRun}, autostart: {installed}}
/api/service-installPOSTRuns sap-devs service install, returns result
/api/service-uninstallPOSTRuns sap-devs service uninstall, returns result
/api/autostart-installPOSTRegisters tray autostart via trayctl, returns result
/api/autostart-uninstallPOSTUnregisters tray autostart, returns result

Validation on POST /api/config:

The backend validates all fields before writing to disk. On failure, returns HTTP 400 with:

json
{
  "errors": {
    "company_repo": "Must be a valid URL (https://...)",
    "sync.tips": "Invalid duration format"
  }
}

On success, writes config.yaml and returns HTTP 200 with {"status": "ok"}.

Frontend

New files in cmd/sap-devs-tray/frontend/:

  • config.html — config editor page
  • js/config.js — form logic, typeahead, validation, save
  • Existing css/app.css extended with config-specific styles

Uses SAP Fundamental Styles components (fd-panel, fd-form-item, fd-input, fd-select, fd-switch, fd-button) with sap_horizon / sap_horizon_dark themes, auto-switching via prefers-color-scheme media queries.

Embedding cities.json

The tray binary lives in a separate Go module and cannot import internal/geo. A copy of cities.json is embedded in the tray binary (e.g. cmd/sap-devs-tray/data/cities.json with //go:embed). The build script (build.ps1) or CI copies the file from internal/geo/cities.json at build time. CI can verify the copy stays in sync.

UI Layout

Single scrolling form with 5 collapsible Fiori panels, all expanded by default. Sticky save bar at the bottom.

Panel 1: General

FieldInput TypeValidation
LanguageSelect dropdownOptions from /api/languages + "(auto-detect from OS)"
LocationText input with typeahead + Detect buttonAutocomplete from /api/cities; soft warning if no match in city database
Experience LevelSelect dropdown(not set), beginner, intermediate, advanced
Company RepoText inputMust be valid URL (https://...) or empty

Location typeahead: On keyup (debounced 200ms), calls GET /api/cities?q=<input>. Backend returns top 10 matches. Frontend renders a dropdown list; clicking an entry fills the input as "City, Country".

Detect button: Calls POST /api/detect-location. On success, populates the input with "City, Country". On failure, shows inline error.

Soft warning: If the entered location text doesn't match any city in the database (checked client-side against typeahead results or via a validation response), show: "Location not found in city database — event filtering may not work." This does not block save.

Panel 2: Preferences

FieldInput TypeValidation
Tip RotationSelect dropdownDaily, Hourly, Session
Interactive TutorialsFiori switch (fd-switch)Boolean toggle

Panel 3: Events

Fields laid out in two-column rows where they pair naturally.

FieldInput TypeValidation
Local RadiusNumber input + "km" suffixInteger, > 0
Regional RadiusNumber input + "km" suffixInteger, > 0
Notify DaysNumber inputInteger, > 0
Notify MethodSelect dropdownHook, OS notification, Both

Panel 4: Sync TTLs

Two-column grid layout to save vertical space. Help text at the bottom: "Go duration format: e.g. 24h, 168h, 4h30m".

FieldInput TypeValidation
Disable All SyncFiori switchBoolean toggle
Tips TTLText inputValid Go duration
Tools TTLText inputValid Go duration
Resources TTLText inputValid Go duration
Context TTLText inputValid Go duration
Events TTLText inputValid Go duration
YouTube TTLText inputValid Go duration
Discovery TTLText inputValid Go duration
Tutorials TTLText inputValid Go duration
Advocates TTLText inputValid Go duration
MCP TTLText inputValid Go duration
Learning TTLText inputValid Go duration

Panel 5: Service & Tray

Two sub-sections separated by a divider.

Background Scheduler:

  • Status badge: "Installed" (green) or "Not Installed" (grey)
  • When installed: shows Interval field (text input, Go duration validation) + "Uninstall Scheduler" button (red outline)
  • When not installed: shows "Install Scheduler" button (primary blue)
  • Install/uninstall are immediate actions (not part of Save flow)

Tray Autostart:

  • Status badge: "Installed" (green) or "Not Installed" (grey)
  • When installed: shows "Uninstall Autostart" button (red outline)
  • When not installed: shows "Install Autostart" button (primary blue)
  • Install/uninstall are immediate actions (not part of Save flow)

Data Flow

Load

  1. User clicks "Config" in tray menu or dashboard link
  2. Wails opens config window at config.html?token=TOKEN
  3. Frontend calls GET /api/config, GET /api/languages, GET /api/service-status in parallel
  4. Form populated with current values; Service & Tray panel renders conditionally based on install state

Save

  1. User clicks Save
  2. Frontend runs client-side validation on all fields
  3. If validation errors: inline error messages shown (red text below field, red border on input), save blocked
  4. If clean: POST /api/config with full config JSON body
  5. Backend validates all fields (duration parsing, URL format, integer ranges)
  6. If backend validation errors: returns {errors: {...&#125;&#125; → frontend maps to field-level inline errors
  7. If success: writes config.yaml, returns {status: "ok"} → frontend shows success message/toast

Service/Tray Actions

These are immediate — not part of the Save flow:

  1. User clicks install/uninstall button
  2. Button shows loading state
  3. Frontend calls appropriate POST /api/service-* or POST /api/autostart-* endpoint
  4. Backend executes sap-devs service install/uninstall (subprocess) or trayctl autostart register/unregister
  5. On success: frontend refreshes service status, updates badge and conditional fields
  6. On failure: inline error message shown

Location Detect

  1. User clicks Detect button → button shows loading state
  2. POST /api/detect-location → backend calls ip-api.com/json (3s timeout)
  3. On success: returns {city, country} → frontend populates input as "City, Country"
  4. On failure: inline message "Could not detect location"

File Changes

FileChange
cmd/sap-devs-tray/config.goNew — config API handlers: read, write, validate, detect-location, cities search, languages list, service/autostart status and actions
cmd/sap-devs-tray/server.goRegister new routes on the existing mux
cmd/sap-devs-tray/app.goAdd config webview window (520×700, titled, resizable); add "Config" menu item; wire srv.configWindowFunc
cmd/sap-devs-tray/data/cities.jsonNew — copy of internal/geo/cities.json, embedded via //go:embed
cmd/sap-devs-tray/frontend/config.htmlNew — config editor page with Fiori panels
cmd/sap-devs-tray/frontend/js/config.jsNew — form population, typeahead, validation, save, service actions
cmd/sap-devs-tray/frontend/css/app.cssAdd styles for config panels, form validation states, typeahead dropdown
cmd/sap-devs-tray/frontend/index.htmlAdd "Config" button in dashboard
cmd/sap-devs-tray/frontend/js/app.jsAdd click handler to open config window
build.ps1Add cities.json copy step before tray build

Constraints

  • The tray binary is a separate Go module — it cannot import from the main CLI's internal/ packages
  • Service install/uninstall run sap-devs as a subprocess (same pattern as sync/inject)
  • Wails v3 is alpha — keep the implementation straightforward
  • Cities.json is copied at build time; CI should verify the copy matches the source
  • The config editor does not handle profile switching (separate concern)