feat(workflow): publish verified forecast controls and consolidated docs
Tests / test (push) Successful in 1m1s

Approved by Daniel Haefliger for develop and beta. Author dh_Agent, authenticated account dh. Preserve published battery, charging and overall Energy Pie changes. No deployment or plant control authorization.
This commit is contained in:
dh
2026-10-08 10:02:58 +00:00
parent af69151165
commit 74a2d6c685
82 changed files with 5054 additions and 617 deletions
+6
View File
@@ -37,6 +37,8 @@ would drift from checkout. The browser refreshes the same catalog endpoint every
- `/docs/utils.html`
- `/docs/faq.html`
- `/docs/preise.html`
- `/docs/versionen.html`: channel availability, new features and known limitations.
- `/docs/ems/changelog.html` and `/docs/utils/changelog.html`: reviewed Git change history.
- `/docs/ems/*.html` and `/docs/utils/*.html`
- `/docs/manifest.json`: published source commits and file mappings.
- `/docs-status.json`: last attempted/successful synchronization, no internal errors.
@@ -50,6 +52,10 @@ The current page's group opens on the server, also without JavaScript. Search
expands matching groups and restores their previous state when cleared. All
page URLs remain unchanged; new references without a group stay accessible.
Explicit links to a different Git branch keep their external Git target.
Only references to the rendered channel are mapped to local documentation;
Stable and Beta comparisons must not silently open the Testing page.
`assets/portal-links.css` contains only the added navigation styling.
`portal-navigation.patch` records the exact additive portal HTML change; check
it against the current portal before applying it and copy the stylesheet to
+4 -3
View File
@@ -55,8 +55,8 @@ article pre code { padding:0; }
.heading-anchor { padding-left:8px; opacity:0; font-weight:400; text-decoration:none; }
h1:hover .heading-anchor,h2:hover .heading-anchor,h3:hover .heading-anchor,.heading-anchor:focus { opacity:1; }
.table-scroll { max-width:100%; overflow-x:auto; margin:20px 0; }
table { border-collapse:collapse; width:100%; font-size:13px; line-height:1.55; text-align:left; }
th { color:#284a45; background:#eef4f1; font-weight:700; }
table { border-collapse:collapse; width:100%; min-width:640px; font-size:13px; line-height:1.55; text-align:left; }
th { color:#284a45; background:#eef4f1; font-weight:700; overflow-wrap:normal; }
th,td { padding:12px; border-bottom:1px solid var(--line); vertical-align:top; }
article table code { overflow-wrap:normal; }
.unpublished { color:inherit; }
@@ -84,5 +84,6 @@ footer p { margin:4px 0; }
[hidden] { display:none!important; }
@media (max-width:1150px) { .docs-layout { grid-template-columns:228px minmax(0,1fr); } .contents { display:none; } main { padding:28px 36px; } }
@media (max-width:720px) { .topbar { position:relative; padding:16px; flex-wrap:wrap; } .topbar nav { gap:20px; font-size:13px; width:100%; justify-content:space-between; } .portal-link { border:0; padding:0; } .docs-layout { display:block; } .sidebar { position:relative; top:0; height:auto; border:0; border-bottom:1px solid var(--line); padding:0 20px; } #docs-menu > summary { display:list-item; padding:15px 0; font-weight:650; cursor:pointer; font-size:14px; } #docs-menu[open] { padding-bottom:20px; } .sidebar nav { display:block; } .sidebar section { margin:16px 0 0; } .sidebar a,.nav-folder > summary { min-height:40px; } main { padding:24px 20px 40px; } article h1 { font-size:29px; } article h2 { font-size:21px; } article { font-size:15px; } .sync-status { margin-bottom:24px; } .price-table { min-width:570px; } article h1,article h2,article h3 { scroll-margin-top:20px; } }
@media print { .topbar,.sidebar,.contents,.price-toolbar,.heading-anchor { display:none; } .docs-layout { display:block; } main { padding:0; } .table-scroll { overflow:visible; } article { font-size:11px; } a { color:inherit; } }
@media (max-width:380px) { article h1 { font-size:26px; } article h1 .heading-anchor { display:none; } }
@media print { .topbar,.sidebar,.contents,.price-toolbar,.heading-anchor { display:none; } .docs-layout { display:block; } main { padding:0; } .table-scroll { overflow:visible; } table { min-width:0; } article { font-size:11px; } a { color:inherit; } }
@media (prefers-reduced-motion:reduce) { * { transition:none!important; scroll-behavior:auto!important; } }
+3 -1
View File
@@ -1,6 +1,7 @@
export const repositories = [
{ id: 'ems', name: 'Enelix-EMS', url: 'https://git.belevo.ch/ENELIX/Enelix-EMS.git', pages: [
['README.md', 'Enelix EMS', 'ems/index.html'],
['CHANGELOG.md', 'EMS-Änderungen', 'ems/changelog.html'],
['docs/module/README.md', 'EMS-Modulübersicht', 'ems/module.html'],
...['Manager', 'Batterie', 'Wassererwaermer', 'Pufferspeicher', 'Verbraucher-1-Stufig', 'Waermepumpe', 'Ladestation-Stand-Alone', 'Ladestation-Gateway', 'Easee-Gateway'].map(name => [`docs/module/${name}/README.md`, name, `ems/${name}.html`]),
['docs/Schnittstelle.md', 'EMS-Schnittstelle', 'ems/schnittstelle.html'],
@@ -9,6 +10,7 @@ export const repositories = [
] },
{ id: 'utils', name: 'Enelix-Utils', url: 'https://git.belevo.ch/ENELIX/Enelix-Utils.git', pages: [
['README.md', 'Enelix Utils', 'utils/index.html'],
['CHANGELOG.md', 'Utils-Änderungen', 'utils/changelog.html'],
['docs/module/README.md', 'Utils-Modulübersicht', 'utils/module.html'],
...['Energiediagramm', 'Shelly-Modul', 'Verbrauchskostenreport', 'CC100-Hardware', 'Virtuelle-Batterie', 'VGT-Schnittstelle'].map(name => [`docs/module/${name}/README.md`, name, `utils/${name}.html`])
] }
@@ -17,5 +19,5 @@ export const repositories = [
export const guides = [
['index', 'Enelix verstehen'], ['erste-schritte', 'Erste Schritte'], ['installation', 'Installation'], ['manager', 'Manager bedienen'],
['verbraucher', 'Verbraucher einrichten'], ['utils', 'Utils nutzen'],
['faq', 'Häufige Fragen'], ['preise', 'Preise']
['versionen', 'Versionen und Fehlerstatus'], ['faq', 'Häufige Fragen'], ['preise', 'Preise']
];
+5 -3
View File
@@ -12,8 +12,10 @@ export function resolveLink(href, page, pages) {
const [path, anchor] = href.split('#', 2);
let key;
if (/^https:\/\/git\.belevo\.ch\/ENELIX\/(Enelix-EMS|Enelix-Utils)\/src\/branch\/[^/]+\//.test(path)) {
const match = path.match(/^https:\/\/git\.belevo\.ch\/ENELIX\/(Enelix-EMS|Enelix-Utils)\/src\/branch\/[^/]+\/(.+)$/);
key = `${match[1]}/${match[2]}`;
const match = path.match(/^https:\/\/git\.belevo\.ch\/ENELIX\/(Enelix-EMS|Enelix-Utils)\/src\/branch\/([^/]+)\/(.+)$/);
// A deliberate channel comparison must not silently link to the current channel.
if (match[2] !== (page.branch || 'develop')) return href;
key = `${match[1]}/${match[3]}`;
} else if (/^https:\/\//.test(href)) {
try { const url = new URL(href); return url.username || url.password ? null : url.href; } catch { return null; }
} else if (/^[\w+.-]+:|^\/\//.test(href) || path.startsWith('/')) return null;
@@ -91,7 +93,7 @@ export function renderNavigation(current, pages) {
}
export function renderPage(page, pages, manifest, assetVersion) {
const { html, toc } = renderMarkdown(page, pages, manifest.images || []);
const { html, toc } = renderMarkdown({ ...page, branch:manifest.branch }, pages, manifest.images || []);
const isPrices = page.output === 'preise.html';
const nav = renderNavigation(page, pages);
const source = page.commit ? `${page.repo} · ${page.commit.slice(0, 12)}` : 'Redaktioneller Entwurf · Git-Veröffentlichung ausstehend';
+12
View File
@@ -8,6 +8,18 @@ import { repositories } from '../config.mjs';
const page = { repo:'Enelix-EMS', source:'docs/public/manager.md', output:'manager.html', title:'Manager', group:'Anleitungen', markdown:'# Manager\n\n## Einrichten\n\nInhalt.', commit:'a'.repeat(40) };
const targets = [page, { repo:'Enelix-EMS', source:'docs/module/Manager/README.md', output:'ems/Manager.html' }, { repo:'Enelix-Utils', source:'docs/module/Energiediagramm/README.md', output:'utils/Energiediagramm.html' }];
test('relative README links remain public', () => assert.equal(resolveLink('../module/Manager/README.md#variablen', page, targets), '/docs/ems/Manager.html#variablen'));
test('explicit other-channel links retain their Git target', () => {
for (const branch of ['main', 'beta']) {
const href = `https://git.belevo.ch/ENELIX/Enelix-EMS/src/branch/${branch}/docs/module/Manager/README.md#variablen`;
assert.equal(resolveLink(href, { ...page, branch:'develop' }, targets), href);
}
});
test('same-channel links map locally and respect the manifest branch', () => {
const href = 'https://git.belevo.ch/ENELIX/Enelix-EMS/src/branch/beta/docs/module/Manager/README.md';
assert.equal(resolveLink(href, { ...page, branch:'beta' }, targets), '/docs/ems/Manager.html');
const html = renderPage({ ...page, markdown:`# Channels\n\n[Beta](${href})` }, targets, { branch:'beta', checkedAt:'2026-10-07T00:00:00Z' }, '123456');
assert.match(html, /href="\/docs\/ems\/Manager.html"/);
});
test('cross-repository links remain public', () => assert.equal(resolveLink('../../../Enelix-Utils/docs/module/Energiediagramm/README.md', page, targets), '/docs/utils/Energiediagramm.html'));
test('private and unsafe targets are not published', () => {
for (const href of ['../NETPLAN_V4_AGENT_HANDOFF.md', '../Offene-Punkte.md', 'javascript:alert(1)', '//evil.example', 'data:text/html,hello', 'https://user:pass@example.com', '/srv/agent/.netrc']) assert.equal(resolveLink(href, page, targets), null);