Handbook
360 Insights Studio — Desktop shell
The Electron app in desktop/ wraps the Python Studio backend (python -m feedback360 --app). It spawns (or attaches to) the local API server, then loads operator screens from desktop/ui/.
Updated
Architecture
desktop/main.js Electron lifecycle, Python spawn, /health polling
desktop/preload.js contextBridge → window.studio (IPC)
desktop/ui/index.html Single-page shell (sidebar + screens)
feedback360/app_server.py Studio HTTP API (127.0.0.1 only)
On launch, main.js:
- Resolves Python (
FEEDBACK360_PYTHON, else repo.venv, elsepython3). - Picks port 8914 (increments up to +20 if busy).
- If
/healthalready returnsservice: "feedback360-studio"on that port, attaches (no spawn). - Otherwise spawns:
python -m feedback360 --app --port <port> --out reportwithcwd= repo root. - Polls
GET /healthfor up to 30s, then loadsui/index.html.
On quit, only a spawned child process is terminated; attached backends are left running.
IPC surface (window.studio)
| Method | Description |
|---|---|
pickPdf() |
Native open dialog (*.pdf); returns absolute path or null |
pickFolder() |
Directory picker; returns path or null |
backendUrl() |
http://127.0.0.1:<port> for the active backend |
openExternal(url) |
Open URL in the system browser |
showInFolder(path) |
Reveal file in file manager |
appInfo() |
{ version, platform } from package.json |
Renderer: contextIsolation: true, nodeIntegration: false.
Screen map
| Screen | ID | Backend routes |
|---|---|---|
| Welcome | screen-welcome |
— |
| New Report | screen-new |
POST /api/generate, GET /api/jobs/<id> |
| Progress | generate-progress |
Job polling (Parse / Analyze / Render / Verify) |
| Workspaces | screen-workspaces |
GET /api/workspaces, POST /api/workspaces/select |
| Report | screen-report |
iframe → GET /report; POST /api/ai/enrich, GET /api/ai/status |
| Live View | screen-live |
iframe → GET / |
| Export | screen-export |
POST /api/export (409 when verify blocks) |
| Settings | screen-settings |
GET/PUT /api/settings, GET /api/ai/status, keystore IPC |
| About | screen-about |
appInfo() |
First-run wizard
On launch, the renderer reads GET /api/settings. When wizard_completed is not
true, a modal wizard appears over the main shell:
- Reports folder (
wizard-step-folder) — showsreports_root; Change… callsPUT /api/settingswith a folder frompickFolder(). - AI narratives (
wizard-step-ai) — sharedrenderAiSetup()cards for Cursor (whencursor_detected), Claude, and OpenAI. Keys are saved viawindow.studio.saveApiKey→ encryptedkeys.json→POST /api/ai/credentials. Skip storesai.provider: nulland continues. - Try it now (
wizard-step-demo) — optional sample run using bundledsources/*.pdf, or skip to Welcome.
Completing the wizard sets wizard_completed: true via PUT /api/settings.
Run setup again in Settings re-opens the wizard without clearing settings.
API key storage (desktop/keystore.js)
createKeystore({ encryptor, storeDir })— injectable for unit tests.- Production wiring in
main.jsuses ElectronsafeStoragewhenisEncryptionAvailable(); otherwise the user must confirm a warning dialog before keys are stored with aplain:prefix (base64 on disk — never the raw key string). - Encrypted blobs live in
<userData>/keystore/keys.json. - On backend boot and after each save,
main.jsdecrypts and postsPOST /api/ai/credentialsso the Python process holds keys in memory only (CredentialStore— never written tosettings.json). - Forget key clears keystore + posts
{ api_key: null }.
IPC: saveApiKey, forgetApiKey, listApiKeyProviders, bundledSources.
Settings screen
screen-settings sections:
- Reports folder — path, Change…, Show in folder.
- AI narratives —
renderAiSetup(), status line, Test connection, Forget key, Advanced model selector (collapsed by default). - About — app version, backend version from
/health, reader wiki link, Run setup again.
User-visible copy avoids technical jargon (no “endpoint”, “token”, “port”, etc.).
Errors use backend message fields or hand-written sentences — never stack traces.
Dev run
./scripts/start-studio.sh
Or manually:
cd desktop && npm install && npm start
Smoke test
Used by orchestration/checks/check-130.py:
cd desktop
FEEDBACK360_PYTHON=../.venv/bin/python npx electron . --no-sandbox --smoke-test
Expects stdout line SMOKE-OK <port> and exit 0 (exit 2 on failure). No windows are created.