feat(docs): Changelog mit "was ist seit meiner Fassung neu"
Bisher musste ein Agent, der eine Anbindung aktualisiert, die gesamte Historie lesen - oder er las gar nichts und uebersah eine brechende Aenderung. Beides schlecht. - public/docs/changelog.json ist die einzige Quelle. Je Fassung eine Zusammenfassung, je Aenderung Bereich, ein "breaking"-Kennzeichen und vor allem ein Feld "action" mit dem, was konkret zu tun ist. Steht dort null, ist nichts zu tun - das ist die haeufigste und nuetzlichste Antwort. - GET /api/updateservice/v1/changelog?since=2.2.0 liefert nur die neueren Fassungen, dazu die Anzahl der Punkte mit Handlungsbedarf und der brechenden Aenderungen. count:0 heisst "du bist auf Stand" - dann muss gar nichts gelesen werden. Optional nach Bereich filterbar (?area=packager). - /docs/changelog.php rendert dieselbe Datei fuer Menschen, mit Eingabefeld fuer die eigene Fassung. Bewusst dieselbe Quelle: zwei Fassungen zu pflegen hiesse, sie auseinanderlaufen zu lassen. - DeploymentcenterSdk.Version im SDK ist der Bezugspunkt. Damit muss die Fassung nicht abgetippt werden. - AGENT_PROMPT_TEMPLATE.md verpflichtet dazu, sie in der AGENTS.md des Projekts festzuhalten und vor jeder Aenderung an der Anbindung den Unterschied abzufragen. Auch in der Kurzfassung fuer knappe Prompt-Budgets. Die Historie ist rueckwirkend bis 2.0.0 gefuellt: 6 Fassungen, 25 Punkte mit Handlungsbedarf, 13 brechende Aenderungen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1967b49ad7
commit
a8b9f6f7c9
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Lesbare Ansicht des Changelogs.
|
||||
*
|
||||
* Quelle ist changelog.json - dieselbe Datei, die
|
||||
* GET /api/updateservice/v1/changelog ausliefert. Zwei Fassungen zu pflegen
|
||||
* hiesse, sie frueher oder spaeter auseinanderlaufen zu lassen.
|
||||
*
|
||||
* /docs/changelog.php alles
|
||||
* /docs/changelog.php?since=2.2.0 nur was seitdem kam
|
||||
* /docs/changelog.php?raw die rohe JSON-Datei
|
||||
*/
|
||||
|
||||
$path = __DIR__ . '/changelog.json';
|
||||
$raw = is_file($path) ? (string)file_get_contents($path) : '';
|
||||
$raw = preg_replace('/^\xEF\xBB\xBF/', '', $raw) ?? $raw;
|
||||
|
||||
if (isset($_GET['raw'])) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo $raw;
|
||||
exit;
|
||||
}
|
||||
|
||||
$decoded = json_decode($raw, true);
|
||||
// Auch bei kaputter Datei ein Array - sonst greift die Ausgabe unten auf null zu.
|
||||
$data = is_array($decoded) ? $decoded : [];
|
||||
$versions = isset($data['versions']) && is_array($data['versions'])
|
||||
? $data['versions']
|
||||
: [];
|
||||
|
||||
$since = isset($_GET['since']) ? trim((string)$_GET['since']) : '';
|
||||
|
||||
/** Semantischer Vergleich - dieselbe Ordnung wie serverseitig. */
|
||||
function cl_core(string $v): array
|
||||
{
|
||||
$v = ltrim(trim($v), 'vV');
|
||||
$plus = strpos($v, '+');
|
||||
if ($plus !== false) { $v = substr($v, 0, $plus); }
|
||||
$dash = strpos($v, '-');
|
||||
if ($dash !== false) { $v = substr($v, 0, $dash); }
|
||||
|
||||
$out = [];
|
||||
foreach (explode('.', $v) as $part) {
|
||||
$out[] = (int)preg_replace('/\D/', '', $part);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
function cl_newer(string $candidate, string $than): bool
|
||||
{
|
||||
$a = cl_core($candidate);
|
||||
$b = cl_core($than);
|
||||
for ($i = 0; $i < max(count($a), count($b)); $i++) {
|
||||
$x = $a[$i] ?? 0;
|
||||
$y = $b[$i] ?? 0;
|
||||
if ($x !== $y) { return $x > $y; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($since !== '') {
|
||||
$versions = array_values(array_filter(
|
||||
$versions,
|
||||
static fn(array $v): bool => cl_newer((string)($v['version'] ?? '0'), $since)
|
||||
));
|
||||
}
|
||||
|
||||
$actionItems = 0;
|
||||
$breaking = 0;
|
||||
foreach ($versions as $v) {
|
||||
foreach ($v['changes'] ?? [] as $c) {
|
||||
if (!empty($c['action'])) { $actionItems++; }
|
||||
if (!empty($c['breaking'])) { $breaking++; }
|
||||
}
|
||||
}
|
||||
|
||||
function e(?string $s): string
|
||||
{
|
||||
return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
$areaLabels = [
|
||||
'server' => 'Server',
|
||||
'sdk' => 'SDK',
|
||||
'agent' => 'Update-Agent',
|
||||
'packager' => 'Packager',
|
||||
'tooling' => 'Werkzeuge',
|
||||
'docs' => 'Dokumentation',
|
||||
];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Deploymentcenter — Änderungen</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0b0f19; --card: #141b2d; --accent: #5b9dff;
|
||||
--text: #e2e8f0; --muted: #94a3b8; --border: #1f2937;
|
||||
--warn: #fbbf24; --break: #f87171; --ok: #4ade80;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; padding: 2rem 1rem; background: var(--bg); color: var(--text);
|
||||
font-family: system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.6; }
|
||||
.wrap { max-width: 60rem; margin: 0 auto; }
|
||||
h1 { margin: 0 0 .25rem; font-size: 1.75rem; }
|
||||
.lead { color: var(--muted); margin: 0 0 1.5rem; }
|
||||
code { font-family: "Fira Code", ui-monospace, monospace; font-size: .85em;
|
||||
background: rgba(91,157,255,.12); padding: .1rem .35rem; border-radius: 3px; }
|
||||
pre code { display: block; padding: .75rem; overflow-x: auto; }
|
||||
.box { background: var(--card); border: 1px solid var(--border);
|
||||
border-radius: 8px; padding: 1rem 1.25rem; margin-bottom: 1.25rem; }
|
||||
.filter { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
|
||||
input[type=text] { background: var(--bg); border: 1px solid var(--border); color: var(--text);
|
||||
padding: .4rem .6rem; border-radius: 5px; font-family: inherit; }
|
||||
button { background: var(--accent); border: 0; color: #041225; font-weight: 600;
|
||||
padding: .45rem .9rem; border-radius: 5px; cursor: pointer; font-family: inherit; }
|
||||
.ver { border-left: 3px solid var(--accent); padding-left: 1rem; margin: 2rem 0; }
|
||||
.ver h2 { margin: 0; font-size: 1.3rem; }
|
||||
.date { color: var(--muted); font-size: .85rem; }
|
||||
.summary { margin: .4rem 0 1rem; }
|
||||
.chg { border-top: 1px solid var(--border); padding: .9rem 0; }
|
||||
.chg:first-of-type { border-top: 0; }
|
||||
.chg h3 { margin: 0 0 .35rem; font-size: 1rem; }
|
||||
.tag { display: inline-block; font-size: .7rem; font-weight: 700; letter-spacing: .03em;
|
||||
padding: .1rem .45rem; border-radius: 4px; margin-right: .4rem; vertical-align: middle; }
|
||||
.t-area { background: rgba(148,163,184,.18); color: var(--muted); }
|
||||
.t-break { background: rgba(248,113,113,.16); color: var(--break); }
|
||||
.t-act { background: rgba(251,191,36,.16); color: var(--warn); }
|
||||
.action { background: rgba(251,191,36,.08); border-left: 2px solid var(--warn);
|
||||
padding: .5rem .75rem; margin-top: .5rem; font-size: .92rem; }
|
||||
.action strong { color: var(--warn); }
|
||||
.none { color: var(--muted); font-style: italic; }
|
||||
a { color: var(--accent); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
|
||||
<h1>Änderungen am Deploymentcenter</h1>
|
||||
<p class="lead">
|
||||
Aktuelle Fassung: <code><?= e($data['current'] ?? '?') ?></code>.
|
||||
Trage ein, gegen welche Fassung deine Anbindung gebaut wurde — dann steht hier
|
||||
nur, was seitdem dazugekommen ist.
|
||||
</p>
|
||||
|
||||
<div class="box">
|
||||
<form method="get" class="filter">
|
||||
<label for="since">Meine Fassung:</label>
|
||||
<input type="text" id="since" name="since" value="<?= e($since) ?>" placeholder="2.2.0" size="10">
|
||||
<button type="submit">Unterschied zeigen</button>
|
||||
<?php if ($since !== ''): ?>
|
||||
<a href="changelog.php" style="margin-left:.5rem;">alles zeigen</a>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<p style="margin:.75rem 0 0; color:var(--muted); font-size:.9rem;">
|
||||
Maschinenlesbar: <code>GET /api/updateservice/v1/changelog?since=<?= e($since !== '' ? $since : '2.2.0') ?></code>
|
||||
· <a href="changelog.php?raw">rohes JSON</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<?php if ($versions === []): ?>
|
||||
<p class="none">
|
||||
<?= $since !== ''
|
||||
? 'Seit ' . e($since) . ' hat sich nichts geändert.'
|
||||
: 'Es ist nichts hinterlegt.' ?>
|
||||
</p>
|
||||
<?php else: ?>
|
||||
<div class="box">
|
||||
<strong><?= count($versions) ?></strong> Fassung(en)<?= $since !== '' ? ' neuer als ' . e($since) : '' ?>,
|
||||
davon <strong style="color:var(--warn);"><?= $actionItems ?></strong> Punkt(e) mit Handlungsbedarf
|
||||
und <strong style="color:var(--break);"><?= $breaking ?></strong> mit Bruch.
|
||||
</div>
|
||||
|
||||
<?php foreach ($versions as $v): ?>
|
||||
<div class="ver">
|
||||
<h2><?= e($v['version'] ?? '?') ?>
|
||||
<span class="date">— <?= e($v['date'] ?? '') ?></span>
|
||||
</h2>
|
||||
<p class="summary"><?= e($v['summary'] ?? '') ?></p>
|
||||
|
||||
<?php foreach ($v['changes'] ?? [] as $c): ?>
|
||||
<div class="chg">
|
||||
<h3>
|
||||
<span class="tag t-area"><?= e($areaLabels[$c['area'] ?? ''] ?? ($c['area'] ?? '?')) ?></span>
|
||||
<?php if (!empty($c['breaking'])): ?><span class="tag t-break">BRUCH</span><?php endif; ?>
|
||||
<?php if (!empty($c['action'])): ?><span class="tag t-act">TUN</span><?php endif; ?>
|
||||
<?= e($c['title'] ?? '') ?>
|
||||
</h3>
|
||||
<div><?= e($c['text'] ?? '') ?></div>
|
||||
<?php if (!empty($c['action'])): ?>
|
||||
<div class="action"><strong>Zu tun:</strong> <?= e($c['action']) ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<p style="margin-top:2.5rem; color:var(--muted); font-size:.9rem;">
|
||||
<a href="/docs/">Agenten-Handbuch</a> ·
|
||||
<a href="/docs/release.md">Veröffentlichen</a> ·
|
||||
<a href="/api/openapi.json">OpenAPI</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user