Feat v0.7.3 extension shell migration (#57)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m51s
CI/CD / test-sqlite (push) Successful in 3m2s
CI/CD / build-and-deploy (push) Successful in 27s
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m51s
CI/CD / test-sqlite (push) Successful in 3m2s
CI/CD / build-and-deploy (push) Successful in 27s
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #57.
This commit is contained in:
@@ -7,9 +7,10 @@
|
||||
#
|
||||
# Pipeline:
|
||||
# 0. Detect changes (path-based gating for all downstream jobs)
|
||||
# 1a. Frontend tests — skipped if only BE/docs changed
|
||||
# 1a. Frontend tests — skipped if only BE/docs/packages changed
|
||||
# 1b. Go unit tests — all non-DB packages + SQLite integration (race-enabled)
|
||||
# 1c. Go test (PG) — PG store + handlers against Postgres (race-enabled)
|
||||
# 1d. Test runners — disabled until v0.7.5 (Playwright headless fix)
|
||||
# 2. Build + Deploy — skipped if docs-only change
|
||||
#
|
||||
# Test coverage mapping (no package tested by zero jobs):
|
||||
@@ -23,7 +24,9 @@
|
||||
# Path gating rules:
|
||||
# src/, src/editor/ → frontend tests
|
||||
# server/, scripts/db-* → backend tests (PG + SQLite)
|
||||
# packages/ → test-runners (surface/extension tests)
|
||||
# Dockerfile*, k8s/, .gitea/ → all tests (infra change)
|
||||
# ci/ → infra (CI scripts)
|
||||
# docs/, *.md → skip all tests + deploy
|
||||
# VERSION, scripts/* → frontend + backend tests
|
||||
# Tags (v*) → always full pipeline
|
||||
@@ -100,6 +103,7 @@ jobs:
|
||||
outputs:
|
||||
frontend: ${{ steps.filter.outputs.frontend }}
|
||||
backend: ${{ steps.filter.outputs.backend }}
|
||||
packages: ${{ steps.filter.outputs.packages }}
|
||||
infra: ${{ steps.filter.outputs.infra }}
|
||||
docs_only: ${{ steps.filter.outputs.docs_only }}
|
||||
steps:
|
||||
@@ -137,7 +141,7 @@ jobs:
|
||||
echo "${CHANGED}" | sed 's/^/ /'
|
||||
|
||||
# Classify
|
||||
FE=false; BE=false; INFRA=false; DOCS=false; OTHER=false
|
||||
FE=false; BE=false; PKG=false; INFRA=false; DOCS=false; OTHER=false
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
case "$file" in
|
||||
@@ -145,19 +149,23 @@ jobs:
|
||||
FE=true ;;
|
||||
server/*|scripts/db-*)
|
||||
BE=true ;;
|
||||
packages/*)
|
||||
PKG=true ;;
|
||||
.gitea/*|k8s/*|Dockerfile*|docker-compose*|docker-entrypoint*|nginx.conf)
|
||||
INFRA=true ;;
|
||||
docs/*|*.md|CHANGELOG.md|LICENSE)
|
||||
DOCS=true ;;
|
||||
VERSION|scripts/*)
|
||||
FE=true; BE=true ;;
|
||||
ci/*)
|
||||
INFRA=true ;;
|
||||
*)
|
||||
OTHER=true ;;
|
||||
esac
|
||||
done <<< "${CHANGED}"
|
||||
|
||||
# Docs-only: only docs changed, nothing else
|
||||
if [[ "$DOCS" == "true" && "$FE" == "false" && "$BE" == "false" && "$INFRA" == "false" && "$OTHER" == "false" ]]; then
|
||||
if [[ "$DOCS" == "true" && "$FE" == "false" && "$BE" == "false" && "$PKG" == "false" && "$INFRA" == "false" && "$OTHER" == "false" ]]; then
|
||||
DOCS_ONLY=true
|
||||
else
|
||||
DOCS_ONLY=false
|
||||
@@ -165,6 +173,7 @@ jobs:
|
||||
|
||||
echo "frontend=${FE}" >> "$GITHUB_OUTPUT"
|
||||
echo "backend=${BE}" >> "$GITHUB_OUTPUT"
|
||||
echo "packages=${PKG}" >> "$GITHUB_OUTPUT"
|
||||
echo "infra=${INFRA}" >> "$GITHUB_OUTPUT"
|
||||
echo "docs_only=${DOCS_ONLY}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -172,6 +181,7 @@ jobs:
|
||||
echo "━━━ Change Detection ━━━"
|
||||
echo " frontend: ${FE}"
|
||||
echo " backend: ${BE}"
|
||||
echo " packages: ${PKG}"
|
||||
echo " infra: ${INFRA}"
|
||||
echo " docs_only: ${DOCS_ONLY}"
|
||||
|
||||
@@ -375,12 +385,13 @@ jobs:
|
||||
# test-runners surface after deploy until this is resolved.
|
||||
# See: docker-compose.ci.yml, ci/surface-test-driver.js
|
||||
#
|
||||
# Runs when: backend or frontend changed (runners test both tiers).
|
||||
# Runs when: backend, frontend, or packages changed (runners test all tiers).
|
||||
# Skipped when: only docs changed.
|
||||
test-runners:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [detect-changes]
|
||||
if: false # disabled — see comment above
|
||||
if: false # disabled — see comment above. Condition for v0.7.5:
|
||||
# needs.detect-changes.outputs.backend == 'true' || needs.detect-changes.outputs.frontend == 'true' || needs.detect-changes.outputs.packages == 'true' || needs.detect-changes.outputs.infra == 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
21
CHANGELOG.md
21
CHANGELOG.md
@@ -2,6 +2,27 @@
|
||||
|
||||
All notable changes to Armature are documented here.
|
||||
|
||||
## v0.7.3 — Extension Shell Migration
|
||||
|
||||
**Shell Topbar Migration**
|
||||
- Migrated Chat, Notes, and Schedules from legacy `sw.shell.Topbar` component to the v0.7.0 shell topbar contract (`sw.shell.topbar.setTitle/setSlot`)
|
||||
- Eliminated double topbar (shell-injected + surface-owned) on all three extension surfaces
|
||||
- Chat: reactive slot updates for thread title + People button when conversation changes
|
||||
- Notes: slot content for + New Note, Import .md, and Graph toggle buttons
|
||||
- Schedules: reactive slot with schedule count and + New Schedule button; removed legacy fallback branch
|
||||
|
||||
**Runner Test Updates**
|
||||
- Added `shell-topbar` test suite to chat-runner, notes-runner, and schedules-runner
|
||||
- Tests fetch surface JS and assert: no legacy `sw.shell.Topbar` reference, uses `sw.shell.topbar.setTitle/setSlot` API
|
||||
- 6 new tests across 3 runners
|
||||
|
||||
**Package Versions**
|
||||
- Chat surface v0.3.0, Notes surface v0.9.0, Schedules surface v0.2.0
|
||||
- Chat runner v0.2.0, Notes runner v0.2.0, Schedules runner v0.2.0
|
||||
|
||||
**Roadmap**
|
||||
- Headless E2E automation moved to v0.7.5 (independent from shell migration)
|
||||
|
||||
## v0.7.1 — Surface Runner Framework
|
||||
|
||||
**`sw.testing` SDK Module**
|
||||
|
||||
34
ROADMAP.md
34
ROADMAP.md
@@ -1,6 +1,6 @@
|
||||
# Armature — Roadmap
|
||||
|
||||
## Current: v0.7.2 — Package Runners + CI Gate
|
||||
## Current: v0.7.3 — Extension Shell Migration
|
||||
|
||||
Self-hosted extensible platform. Auth, identity, packages, Starlark sandbox,
|
||||
storage, realtime, and ops are kernel primitives. Everything else is an extension.
|
||||
@@ -152,9 +152,7 @@ Design doc: `docs/DESIGN-surface-runners.md`
|
||||
| CI integration | done | `test-runners` stage in Gitea CI. Playwright driver, `wait-for-healthy.sh`. |
|
||||
| CI DinD networking fix | done | Resolve container IP via `docker inspect` — DinD port mapping doesn't expose to runner localhost. |
|
||||
|
||||
### v0.7.3 — Extension Shell Migration + Headless E2E
|
||||
|
||||
**Extension Shell Migration**
|
||||
### v0.7.3 — Extension Shell Migration
|
||||
|
||||
Chat, Notes, and Schedules still use the old `sw.shell.Topbar` component,
|
||||
producing a double topbar (shell-injected + surface-owned). Migrate all
|
||||
@@ -163,20 +161,10 @@ same pattern as the kernel surface migrations.
|
||||
|
||||
| Step | Status | Description |
|
||||
|------|--------|-------------|
|
||||
| Chat → shell topbar | | Delete `<Topbar>`, use `setTitle('Chat')` + `setSlot()` for thread title/people button. |
|
||||
| Notes → shell topbar | | Delete `<Topbar>`, use `setTitle('Notes')` + `setSlot()` for sidebar tabs/search. |
|
||||
| Schedules → shell topbar | | Delete `<Topbar>`, use `setTitle('Schedules')`. Pattern A (title only). |
|
||||
| Update package runner tests | | Assert shell topbar present, no double topbar, on migrated surfaces. |
|
||||
|
||||
**Headless E2E Automation**
|
||||
|
||||
| Step | Status | Description |
|
||||
|------|--------|-------------|
|
||||
| Playwright test harness | | `ci/e2e-surface-test.sh` — docker-compose, chromium, run-all, assert. |
|
||||
| Screenshot-on-failure | | Full-page screenshot + console log. CI artifacts. |
|
||||
| Navigation smoke test | | Playwright visits every surface. Asserts topbar, no JS errors, home link works. |
|
||||
| Visual regression baseline | | Optional screenshot diff. Not a gate — report for review. |
|
||||
| CI pipeline integration | | After unit tests + API runners. Failure blocks merge. |
|
||||
| Chat → shell topbar | done | Delete `<Topbar>`, use `setTitle('Chat')` + `setSlot()` for thread title/people button. |
|
||||
| Notes → shell topbar | done | Delete `<Topbar>`, use `setTitle('Notes')` + `setSlot()` for action buttons. |
|
||||
| Schedules → shell topbar | done | Delete `<Topbar>`, use `setTitle('Schedules')` + `setSlot()` for count/new button. |
|
||||
| Update package runner tests | done | Shell-topbar test suite in each runner — assert no legacy Topbar, uses shell API. |
|
||||
|
||||
### v0.7.4 — Documentation + Deferred Surface Work
|
||||
|
||||
@@ -188,6 +176,16 @@ same pattern as the kernel surface migrations.
|
||||
| Extension config section docs | | Document `config_section` manifest field for Settings/Admin extensibility. |
|
||||
| Team Admin Workflows evaluation | | 723-line inline designer — extract to surface or split into files. Decision doc if needed. |
|
||||
|
||||
### v0.7.5 — Headless E2E + CI Gate
|
||||
|
||||
| Step | Status | Description |
|
||||
|------|--------|-------------|
|
||||
| Playwright test harness | | `ci/e2e-surface-test.sh` — docker-compose, chromium, run-all, assert. |
|
||||
| Screenshot-on-failure | | Full-page screenshot + console log. CI artifacts. |
|
||||
| Navigation smoke test | | Playwright visits every surface. Asserts topbar, no JS errors, home link works. |
|
||||
| Visual regression baseline | | Optional screenshot diff. Not a gate — report for review. |
|
||||
| CI pipeline integration | | Enable `test-runners` stage, add `e2e-smoke` stage. Failure blocks merge. |
|
||||
|
||||
---
|
||||
|
||||
## Post-v0.7.x
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
|
||||
var modules = [
|
||||
'conversations.js',
|
||||
'messaging.js'
|
||||
'messaging.js',
|
||||
'shell-topbar.js'
|
||||
];
|
||||
|
||||
var loaded = 0;
|
||||
|
||||
31
packages/chat-runner/js/shell-topbar.js
Normal file
31
packages/chat-runner/js/shell-topbar.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Chat Runner — chat/shell-topbar suite
|
||||
*
|
||||
* Validates the Chat surface uses the v0.7.0 shell topbar contract
|
||||
* and does not render the legacy sw.shell.Topbar component.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
sw.testing.suite('chat/shell-topbar', async function (s) {
|
||||
|
||||
s.test('surface JS does not reference legacy Topbar', async function (t) {
|
||||
var base = window.__BASE__ || '';
|
||||
var resp = await fetch(base + '/surfaces/chat/js/main.js');
|
||||
t.assert.eq(resp.status, 200, 'fetched chat main.js');
|
||||
var src = await resp.text();
|
||||
var hasLegacy = src.indexOf('sw.shell.Topbar') !== -1;
|
||||
t.assert.ok(!hasLegacy, 'no sw.shell.Topbar reference (uses shell topbar API)');
|
||||
});
|
||||
|
||||
s.test('surface JS uses shell topbar API', async function (t) {
|
||||
var base = window.__BASE__ || '';
|
||||
var resp = await fetch(base + '/surfaces/chat/js/main.js');
|
||||
var src = await resp.text();
|
||||
var usesAPI = src.indexOf('sw.shell.topbar.setTitle') !== -1
|
||||
|| src.indexOf('sw.shell.topbar.setSlot') !== -1;
|
||||
t.assert.ok(usesAPI, 'uses sw.shell.topbar.setTitle or setSlot');
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
||||
@@ -4,6 +4,6 @@
|
||||
"type": "test-runner",
|
||||
"title": "Chat Runner",
|
||||
"auth": "admin",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "Integration tests for Chat package — conversations, messaging, search."
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* Chat — Surface Entry Point (v0.2.0)
|
||||
* Chat — Surface Entry Point (v0.3.0)
|
||||
*
|
||||
* Messaging surface built on chat-core library:
|
||||
* sw.api.ext('chat-core') — conversation/message CRUD
|
||||
* sw.api.ext('chat') — typing indicators
|
||||
* sw.realtime — live events
|
||||
* sw.ui.* — primitive components
|
||||
* sw.shell.Topbar — navigation bar
|
||||
* sw.shell.topbar — shell topbar API
|
||||
*/
|
||||
(async function () {
|
||||
'use strict';
|
||||
@@ -42,7 +42,6 @@
|
||||
var api = sw.api.ext('chat-core');
|
||||
var chatApi = sw.api.ext('chat');
|
||||
var { Button, Spinner, Avatar, Dialog, Tabs } = sw.ui;
|
||||
var Topbar = sw.shell.Topbar;
|
||||
|
||||
// Import UserPicker directly (not in sw.ui index)
|
||||
var { UserPicker } = await import(base + '/js/sw/primitives/user-picker.js?v=' + ver);
|
||||
@@ -831,20 +830,33 @@
|
||||
var selectedConv = conversations.find(c => c.id === selectedId);
|
||||
var threadTitle = selectedConv ? (selectedConv.title || 'Direct Message') : '';
|
||||
|
||||
// ── Shell topbar ───────────────────────────
|
||||
useEffect(() => {
|
||||
if (!sw.shell?.topbar) return;
|
||||
sw.shell.topbar.setTitle('Chat');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sw.shell?.topbar) return;
|
||||
if (selectedId) {
|
||||
sw.shell.topbar.setSlot(html`
|
||||
<span class="ext-chat-topbar__thread-title">${threadTitle}</span>
|
||||
<${Button} size="sm" variant="secondary"
|
||||
onClick=${() => setShowParticipants(!showParticipants)}>
|
||||
${showParticipants ? 'Hide' : 'People'}
|
||||
<//>
|
||||
`);
|
||||
} else {
|
||||
sw.shell.topbar.setSlot(null);
|
||||
}
|
||||
}, [selectedId, threadTitle, showParticipants]);
|
||||
|
||||
if (loading) {
|
||||
return html`<div class="ext-chat-loading"><${Spinner} /></div>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="ext-chat-app">
|
||||
<${Topbar} title="Chat">
|
||||
${selectedId && html`
|
||||
<span class="ext-chat-topbar__thread-title">${threadTitle}</span>
|
||||
<${Button} size="sm" variant="secondary"
|
||||
onClick=${() => setShowParticipants(!showParticipants)}>
|
||||
${showParticipants ? 'Hide' : 'People'}
|
||||
<//>`}
|
||||
<//>
|
||||
<div class="ext-chat-body">
|
||||
<${ConversationList}
|
||||
selected=${selectedId}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"route": "/s/chat",
|
||||
"auth": "authenticated",
|
||||
"layout": "single",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"icon": "\ud83d\udcac",
|
||||
"description": "Chat surface — conversations, messaging, typing indicators, read receipts.",
|
||||
"author": "armature",
|
||||
|
||||
@@ -36,7 +36,8 @@
|
||||
var modules = [
|
||||
'notes-crud.js',
|
||||
'folders.js',
|
||||
'tags-search.js'
|
||||
'tags-search.js',
|
||||
'shell-topbar.js'
|
||||
];
|
||||
|
||||
var loaded = 0;
|
||||
|
||||
31
packages/notes-runner/js/shell-topbar.js
Normal file
31
packages/notes-runner/js/shell-topbar.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Notes Runner — notes/shell-topbar suite
|
||||
*
|
||||
* Validates the Notes surface uses the v0.7.0 shell topbar contract
|
||||
* and does not render the legacy sw.shell.Topbar component.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
sw.testing.suite('notes/shell-topbar', async function (s) {
|
||||
|
||||
s.test('surface JS does not reference legacy Topbar', async function (t) {
|
||||
var base = window.__BASE__ || '';
|
||||
var resp = await fetch(base + '/surfaces/notes/js/main.js');
|
||||
t.assert.eq(resp.status, 200, 'fetched notes main.js');
|
||||
var src = await resp.text();
|
||||
var hasLegacy = src.indexOf('sw.shell.Topbar') !== -1;
|
||||
t.assert.ok(!hasLegacy, 'no sw.shell.Topbar reference (uses shell topbar API)');
|
||||
});
|
||||
|
||||
s.test('surface JS uses shell topbar API', async function (t) {
|
||||
var base = window.__BASE__ || '';
|
||||
var resp = await fetch(base + '/surfaces/notes/js/main.js');
|
||||
var src = await resp.text();
|
||||
var usesAPI = src.indexOf('sw.shell.topbar.setTitle') !== -1
|
||||
|| src.indexOf('sw.shell.topbar.setSlot') !== -1;
|
||||
t.assert.ok(usesAPI, 'uses sw.shell.topbar.setTitle or setSlot');
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
||||
@@ -4,6 +4,6 @@
|
||||
"type": "test-runner",
|
||||
"title": "Notes Runner",
|
||||
"auth": "admin",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "Integration tests for Notes package — CRUD, folders, tags, backlinks, search."
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/**
|
||||
* Notes — Surface Entry Point (v0.8.0)
|
||||
* Notes — Surface Entry Point (v0.9.0)
|
||||
*
|
||||
* Markdown notes surface using the SDK:
|
||||
* sw.api.ext('notes') — scoped API client
|
||||
* sw.ui.* — primitive components
|
||||
* sw.shell.Topbar — navigation bar
|
||||
* sw.shell.topbar — shell topbar API
|
||||
* window.CM — CodeMirror 6 bundle (optional)
|
||||
*/
|
||||
(async function () {
|
||||
@@ -59,7 +59,6 @@
|
||||
// ── SDK modules ────────────────────────────
|
||||
var api = sw.api.ext('notes');
|
||||
var { Button, Spinner } = sw.ui;
|
||||
var Topbar = sw.shell.Topbar;
|
||||
|
||||
// ── Debounce helper ────────────────────────
|
||||
var _timers = {};
|
||||
@@ -1729,13 +1728,19 @@
|
||||
debounce('search', function() { loadNotes(); }, 300);
|
||||
}
|
||||
|
||||
return html`
|
||||
<${Topbar} title="Notes">
|
||||
// ── Shell topbar ───────────────────────────
|
||||
useEffect(() => {
|
||||
if (!sw.shell?.topbar) return;
|
||||
sw.shell.topbar.setTitle('Notes');
|
||||
sw.shell.topbar.setSlot(html`
|
||||
<${Button} variant="primary" size="sm" onClick=${handleNew}>+ New Note<//>
|
||||
<${Button} variant="secondary" size="sm" onClick=${handleImport}>Import .md<//>
|
||||
<${Button} variant=${showGraph ? 'primary' : 'secondary'} size="sm"
|
||||
onClick=${function() { setShowGraph(!showGraph); }}>Graph<//>
|
||||
<//>
|
||||
`);
|
||||
}, [showGraph]);
|
||||
|
||||
return html`
|
||||
<div class="ext-notes-app">
|
||||
<div class="ext-notes-sidebar">
|
||||
<${SidebarTabs} activeTab=${sidebarTab} onTabChange=${setSidebarTab} noteSelected=${!!activeNote} />
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"route": "/s/notes",
|
||||
"auth": "authenticated",
|
||||
"layout": "single",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.0",
|
||||
"icon": "📝",
|
||||
"description": "Markdown notes surface with rich editor, folders, tags, backlinks, import/export, sidebar tabs, document outline, and note graph.",
|
||||
"author": "armature",
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
if (window.SR.base) assetBase = window.SR.base + assetBase;
|
||||
|
||||
var modules = [
|
||||
'schedules-crud.js'
|
||||
'schedules-crud.js',
|
||||
'shell-topbar.js'
|
||||
];
|
||||
|
||||
var loaded = 0;
|
||||
|
||||
31
packages/schedules-runner/js/shell-topbar.js
Normal file
31
packages/schedules-runner/js/shell-topbar.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Schedules Runner — schedules/shell-topbar suite
|
||||
*
|
||||
* Validates the Schedules surface uses the v0.7.0 shell topbar contract
|
||||
* and does not render the legacy sw.shell.Topbar component.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
sw.testing.suite('schedules/shell-topbar', async function (s) {
|
||||
|
||||
s.test('surface JS does not reference legacy Topbar', async function (t) {
|
||||
var base = window.__BASE__ || '';
|
||||
var resp = await fetch(base + '/surfaces/schedules/js/main.js');
|
||||
t.assert.eq(resp.status, 200, 'fetched schedules main.js');
|
||||
var src = await resp.text();
|
||||
var hasLegacy = src.indexOf('sw.shell.Topbar') !== -1;
|
||||
t.assert.ok(!hasLegacy, 'no sw.shell.Topbar reference (uses shell topbar API)');
|
||||
});
|
||||
|
||||
s.test('surface JS uses shell topbar API', async function (t) {
|
||||
var base = window.__BASE__ || '';
|
||||
var resp = await fetch(base + '/surfaces/schedules/js/main.js');
|
||||
var src = await resp.text();
|
||||
var usesAPI = src.indexOf('sw.shell.topbar.setTitle') !== -1
|
||||
|| src.indexOf('sw.shell.topbar.setSlot') !== -1;
|
||||
t.assert.ok(usesAPI, 'uses sw.shell.topbar.setTitle or setSlot');
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
||||
@@ -4,6 +4,6 @@
|
||||
"type": "test-runner",
|
||||
"title": "Schedules Runner",
|
||||
"auth": "admin",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "Integration tests for Schedules package — CRUD, run, enable/disable."
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* this surface talks directly to the core scheduled-tasks endpoints.
|
||||
*
|
||||
* SDK usage:
|
||||
* sw.shell.Topbar — standard navigation bar
|
||||
* sw.shell.topbar — shell topbar API
|
||||
* sw.ui.* — primitive components
|
||||
* sw.api.* — raw REST calls to /api/v1/schedules
|
||||
*/
|
||||
@@ -39,7 +39,6 @@
|
||||
var { useState, useEffect, useCallback } = hooks;
|
||||
var { render } = preact;
|
||||
var { Button, FormField, Dialog, Spinner, Banner } = sw.ui;
|
||||
var Topbar = sw.shell?.Topbar;
|
||||
|
||||
// ── API helpers (core kernel endpoints) ────
|
||||
var API = '/api/v1/schedules';
|
||||
@@ -365,6 +364,16 @@
|
||||
|
||||
useEffect(function () { loadItems(); }, []);
|
||||
|
||||
// ── Shell topbar ───────────────────────────
|
||||
useEffect(function () {
|
||||
if (!sw.shell?.topbar) return;
|
||||
sw.shell.topbar.setTitle('Schedules');
|
||||
sw.shell.topbar.setSlot(html`
|
||||
<span class="ext-schedules-count">${items.length} schedule${items.length !== 1 ? 's' : ''}</span>
|
||||
<${Button} variant="primary" size="sm" onClick=${() => setEditing({})}>+ New Schedule<//>
|
||||
`);
|
||||
}, [items.length]);
|
||||
|
||||
function handleSave() {
|
||||
setEditing(null);
|
||||
loadItems();
|
||||
@@ -372,17 +381,6 @@
|
||||
|
||||
return html`
|
||||
<div class="ext-schedules-app">
|
||||
${Topbar ? html`
|
||||
<${Topbar} title="Schedules">
|
||||
<span class="ext-schedules-count">${items.length} schedule${items.length !== 1 ? 's' : ''}</span>
|
||||
<${Button} variant="primary" size="sm" onClick=${() => setEditing({})}>+ New Schedule<//>
|
||||
<//>
|
||||
` : html`
|
||||
<div class="ext-schedules-header">
|
||||
<h1>Schedules</h1>
|
||||
<${Button} variant="primary" size="sm" onClick=${() => setEditing({})}>+ New Schedule<//>
|
||||
</div>
|
||||
`}
|
||||
|
||||
${error && html`<${Banner} variant="danger" text=${error} />`}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "schedules",
|
||||
"title": "Schedules",
|
||||
"type": "full",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"tier": "browser",
|
||||
"author": "Armature",
|
||||
"icon": "⏰",
|
||||
|
||||
Reference in New Issue
Block a user