diff --git a/AGENTS.md b/AGENTS.md index db2412f..40dd89a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,7 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`. - **Frontend (Vue 3)**: `frontend/` (served by the service) - **Integration tests**: `integration-tests/` - **Protos/Generated**: `proto/`, `service/gen/...` +- **Specs**: `specs/` — Markdown specs that define how code should behave in human-readable form. When changing behavior in a spec-covered area, keep implementation and tests aligned with the spec; do not reference code or symbols in specs (English only). ### How to Run - Run the server (dev): @@ -62,11 +63,10 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`. ### Contributing Checklist - Review the contributing guidelines at `CONTRIBUTING.adoc`. - Review the AI guidance in `AI.md`. -- Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`. +- Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`. +- When changing behaviour covered by a spec in `specs/`, ensure implementation and tests match the spec. ### Troubleshooting - API tests failing with content-type errors: ensure Connect handler is served under `/api/` and the client targets that base URL. - Executor panics: check for nil `Binding/Action` and add guards in step functions. - Integration timeouts: wait for `loaded-dashboard` and use selectors matching the Vue UI. - - diff --git a/frontend/js/websocket.js b/frontend/js/websocket.js index 8c0200f..0fffb88 100644 --- a/frontend/js/websocket.js +++ b/frontend/js/websocket.js @@ -1,5 +1,8 @@ import { buttonResults } from '../resources/vue/stores/buttonResults.js' import { rateLimits } from '../resources/vue/stores/rateLimits.js' +import { connectionState } from '../resources/vue/stores/connectionState.js' + +const RECONNECT_DELAY_MS = 10000 export function initWebsocket () { window.addEventListener('EventOutputChunk', onOutputChunk) @@ -16,9 +19,21 @@ async function reconnectWebsocket () { return } + connectionState.reconnecting = true + connectionState.connected = false + if (connectionState.disconnectedAt == null) { + connectionState.disconnectedAt = Date.now() + } + connectionState.nextReconnectAt = null + try { window.websocketAvailable = true - for await (const e of window.client.eventStream()) { + const stream = window.client.eventStream() + connectionState.connected = true + connectionState.reconnecting = false + connectionState.nextReconnectAt = null + for await (const e of stream) { + connectionState.disconnectedAt = null handleEvent(e) } } catch (err) { @@ -26,7 +41,13 @@ async function reconnectWebsocket () { } window.websocketAvailable = false - console.log('Reconnecting websocket...') + connectionState.connected = false + connectionState.disconnectedAt = connectionState.disconnectedAt ?? Date.now() + connectionState.nextReconnectAt = Date.now() + RECONNECT_DELAY_MS + console.log('Reconnecting websocket in ' + RECONNECT_DELAY_MS + 'ms...') + setTimeout(() => { + reconnectWebsocket() + }, RECONNECT_DELAY_MS) } function handleEvent (msg) { diff --git a/frontend/resources/vue/App.vue b/frontend/resources/vue/App.vue index 7dfae4a..dccdaa0 100644 --- a/frontend/resources/vue/App.vue +++ b/frontend/resources/vue/App.vue @@ -7,6 +7,7 @@