@micha.bigler/ui-core-micha 2.41.0 → 2.41.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +4 -4
- package/WORK_ORDERS.md +6 -1
- package/dist/auth/apiClient.js +34 -4
- package/dist/i18n/uiCoreTranslations.js +8 -0
- package/dist/index.js +6 -8
- package/dist/theme/createAppTheme.js +1 -3
- package/dist/theme/fonts.js +3 -0
- package/dist/theme/index.js +1 -0
- package/package.json +13 -1
- package/src/auth/apiClient.jsx +33 -4
- package/src/i18n/uiCoreTranslations.js +17 -0
- package/src/index.js +6 -17
- package/src/theme/createAppTheme.js +1 -3
- package/src/theme/fonts.js +3 -0
- package/src/theme/index.js +2 -0
- package/tests/apiClientPublicPaths.test.js +178 -0
- package/tests/packageTreeShaking.test.js +126 -0
- package/work-orders/AUTH-5.md +154 -0
- package/work-orders/DX-2.md +188 -0
- package/work-orders/DX-3.md +195 -0
- package/work-orders/I18N-3.md +139 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { gzipSync } from 'node:zlib';
|
|
6
|
+
|
|
7
|
+
import { build } from 'vite';
|
|
8
|
+
import { afterAll, expect, test } from 'vitest';
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
11
|
+
const packageManifest = JSON.parse(
|
|
12
|
+
await readFile(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
13
|
+
);
|
|
14
|
+
const packageName = packageManifest.name;
|
|
15
|
+
let temporaryRoot;
|
|
16
|
+
|
|
17
|
+
async function bundlePackageExport({ caseName, exportName, manifestField }) {
|
|
18
|
+
temporaryRoot ||= await mkdtemp(path.join(tmpdir(), 'ui-core-tree-shaking-'));
|
|
19
|
+
const consumerRoot = path.join(temporaryRoot, caseName);
|
|
20
|
+
const installedPackageRoot = path.join(
|
|
21
|
+
consumerRoot,
|
|
22
|
+
'node_modules',
|
|
23
|
+
...packageName.split('/'),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
await mkdir(installedPackageRoot, { recursive: true });
|
|
27
|
+
await cp(
|
|
28
|
+
path.join(packageRoot, 'dist'),
|
|
29
|
+
path.join(installedPackageRoot, 'dist'),
|
|
30
|
+
{ recursive: true },
|
|
31
|
+
);
|
|
32
|
+
const installedFontRoot = path.join(
|
|
33
|
+
consumerRoot,
|
|
34
|
+
'node_modules',
|
|
35
|
+
'@fontsource',
|
|
36
|
+
'dm-sans',
|
|
37
|
+
);
|
|
38
|
+
await mkdir(path.dirname(installedFontRoot), { recursive: true });
|
|
39
|
+
await cp(
|
|
40
|
+
path.join(packageRoot, 'node_modules', '@fontsource', 'dm-sans'),
|
|
41
|
+
installedFontRoot,
|
|
42
|
+
{ recursive: true },
|
|
43
|
+
);
|
|
44
|
+
await writeFile(
|
|
45
|
+
path.join(installedPackageRoot, 'package.json'),
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
name: packageName,
|
|
48
|
+
version: packageManifest.version,
|
|
49
|
+
[manifestField]: packageManifest[manifestField],
|
|
50
|
+
sideEffects: packageManifest.sideEffects,
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const entryPath = path.join(consumerRoot, 'entry.js');
|
|
55
|
+
await writeFile(entryPath, `export { ${exportName} } from '${packageName}';\n`);
|
|
56
|
+
|
|
57
|
+
const result = await build({
|
|
58
|
+
configFile: false,
|
|
59
|
+
logLevel: 'silent',
|
|
60
|
+
root: consumerRoot,
|
|
61
|
+
build: {
|
|
62
|
+
minify: true,
|
|
63
|
+
rollupOptions: {
|
|
64
|
+
input: entryPath,
|
|
65
|
+
preserveEntrySignatures: 'strict',
|
|
66
|
+
external: (id) => (
|
|
67
|
+
id !== packageName
|
|
68
|
+
&& !id.startsWith('@fontsource/dm-sans')
|
|
69
|
+
&& !id.startsWith('.')
|
|
70
|
+
&& !path.isAbsolute(id)
|
|
71
|
+
),
|
|
72
|
+
output: {
|
|
73
|
+
format: 'es',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
write: false,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const outputs = Array.isArray(result)
|
|
80
|
+
? result.flatMap((buildResult) => buildResult.output)
|
|
81
|
+
: result.output;
|
|
82
|
+
const entryChunk = outputs.find((output) => output.type === 'chunk' && output.isEntry);
|
|
83
|
+
|
|
84
|
+
expect(entryChunk, `${manifestField} entry should produce a bundle`).toBeDefined();
|
|
85
|
+
return {
|
|
86
|
+
code: entryChunk.code,
|
|
87
|
+
css: outputs
|
|
88
|
+
.filter((output) => output.type === 'asset' && output.fileName.endsWith('.css'))
|
|
89
|
+
.map((output) => output.source.toString())
|
|
90
|
+
.join('\n'),
|
|
91
|
+
rawBytes: Buffer.byteLength(entryChunk.code),
|
|
92
|
+
gzipBytes: gzipSync(entryChunk.code).byteLength,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
afterAll(async () => {
|
|
97
|
+
if (temporaryRoot) await rm(temporaryRoot, { recursive: true, force: true });
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('built package entries resolve and unused translation bundles tree-shake', async () => {
|
|
101
|
+
const authOnly = await bundlePackageExport({
|
|
102
|
+
caseName: 'auth-main',
|
|
103
|
+
exportName: 'authTranslations',
|
|
104
|
+
manifestField: 'main',
|
|
105
|
+
});
|
|
106
|
+
const aggregate = await bundlePackageExport({
|
|
107
|
+
caseName: 'aggregate-module',
|
|
108
|
+
exportName: 'uiCoreTranslations',
|
|
109
|
+
manifestField: 'module',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
expect(authOnly.code).toContain('Auth.LOGIN');
|
|
113
|
+
expect(authOnly.code).not.toContain('ChartFrame.LOADING');
|
|
114
|
+
expect(authOnly.css).toContain('DM Sans');
|
|
115
|
+
expect(aggregate.code).toContain('ChartFrame.LOADING');
|
|
116
|
+
expect(aggregate.rawBytes).toBeGreaterThan(authOnly.rawBytes);
|
|
117
|
+
expect(aggregate.gzipBytes).toBeGreaterThan(authOnly.gzipBytes);
|
|
118
|
+
|
|
119
|
+
const rawDelta = aggregate.rawBytes - authOnly.rawBytes;
|
|
120
|
+
const gzipDelta = aggregate.gzipBytes - authOnly.gzipBytes;
|
|
121
|
+
console.info(
|
|
122
|
+
`tree-shaking measurement: auth=${authOnly.rawBytes} B raw/${authOnly.gzipBytes} B gzip; `
|
|
123
|
+
+ `aggregate=${aggregate.rawBytes} B raw/${aggregate.gzipBytes} B gzip; `
|
|
124
|
+
+ `delta=+${rawDelta} B raw/+${gzipDelta} B gzip`,
|
|
125
|
+
);
|
|
126
|
+
}, 15_000);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# AUTH-5 — `addPublicPath` muss dynamische Routen ausdrücken können
|
|
2
|
+
|
|
3
|
+
**Target repo:** `ui-core-micha` (branch `main`)
|
|
4
|
+
**Tier:** 3 — shared-core. Die Änderung betrifft die Login-Weiterleitung jeder konsumierenden App.
|
|
5
|
+
|
|
6
|
+
**Kein Prototyp.** Diese Work Order hat keine Oberfläche — sie erweitert eine Registrierungs-API.
|
|
7
|
+
|
|
8
|
+
**Auslöser:** `jg-ferien` PUB-7 Teil 2. Diese WO ist dessen Vorbedingung; nach der Veröffentlichung
|
|
9
|
+
zieht jg seinen Pin von **`2.37.1`** hoch.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## A. Envelope
|
|
14
|
+
|
|
15
|
+
### Goal
|
|
16
|
+
|
|
17
|
+
`apiClient` leitet bei 401/403 per hartem `window.location.assign("/login")` um — **ausser** der
|
|
18
|
+
aktuelle Pfad steht auf einer Allowlist (`redirectToLoginOnce`, `src/auth/apiClient.jsx`). Der
|
|
19
|
+
Mechanismus ist richtig gebaut: auf einer öffentlichen Seite unterbleibt die Weiterleitung und der
|
|
20
|
+
401 wird an die Komponente durchgereicht.
|
|
21
|
+
|
|
22
|
+
**Was er nicht kann, ist dynamische Routen.** `matchesPublicPath` vergleicht mit `startsWith`, ausser
|
|
23
|
+
bei `/`, wo Gleichheit verlangt wird. Eine App mit öffentlichen Seiten unter einem **variablen
|
|
24
|
+
Segment** — `/<slug>`, `/<slug>/team` — kann ihre öffentliche Fläche damit nicht beschreiben. Ein
|
|
25
|
+
konkreter Slug lässt sich eintragen; der nächste, den jemand anlegt, fällt wieder heraus.
|
|
26
|
+
|
|
27
|
+
**Warum das mehr ist als eine Unbequemlichkeit.** Fehlt der Pfad in der Liste, landet ein anonymer
|
|
28
|
+
Besucher beim ersten 401 auf `/login` — auf einer Seite, die öffentlich sein soll. Von aussen ist das
|
|
29
|
+
nicht von „diese Seite gibt es nicht" zu unterscheiden, und der Fehler zeigt sich erst, wenn ein
|
|
30
|
+
zweites Department existiert. Er sieht dann aus wie ein Zufall, nicht wie eine Lücke.
|
|
31
|
+
|
|
32
|
+
**Erwartetes Ergebnis.** Eine konsumierende App kann ihre öffentliche Fläche vollständig beschreiben,
|
|
33
|
+
auch wenn Teile davon aus variablen Segmenten bestehen.
|
|
34
|
+
|
|
35
|
+
### Scope
|
|
36
|
+
|
|
37
|
+
- **`addPublicPath` nimmt zusätzlich ein `RegExp` entgegen** (String bleibt unverändert gültig).
|
|
38
|
+
- **`matchesPublicPath` unterscheidet nach Typ**: String wie bisher (`/` exakt, sonst `startsWith`),
|
|
39
|
+
`RegExp` per `test()` gegen **ausschliesslich `window.location.pathname`** — nie gegen Query oder
|
|
40
|
+
Fragment, damit ein Muster nicht versehentlich über den Pfad hinaus greift.
|
|
41
|
+
- **`removePublicPath` funktioniert für beide Formen.** Bei `RegExp` über Identität des übergebenen
|
|
42
|
+
Objekts; das gehört dokumentiert, weil zwei gleich aussehende Literale nicht dasselbe Objekt sind.
|
|
43
|
+
- **`BUILTIN_PUBLIC_PATHS` bleibt eingefroren und rein string-basiert.** Kein Muster in der
|
|
44
|
+
Bibliotheks-Liste — sie darf sich nicht durch eine Konsumenten-Erweiterung verschieben.
|
|
45
|
+
- **Die Dokumentation am Aufruf sagt, was der Konsument nun selbst trägt**: ein zu weites Muster
|
|
46
|
+
unterdrückt die Weiterleitung dort, wo sie gebraucht wird, und ein abgelaufener Login endet dann in
|
|
47
|
+
einer stillen Fehlerseite statt am Login. Muster gehören verankert (`^…$`).
|
|
48
|
+
|
|
49
|
+
### Non-goals / do not touch
|
|
50
|
+
|
|
51
|
+
- **Keine Änderung am Weiterleitungsverhalten selbst** — weder Ziel, noch `?next=`, noch der
|
|
52
|
+
einmalige Sperrmechanismus (`redirectingToLogin`).
|
|
53
|
+
- **Keine Änderung an der eingefrorenen Builtin-Liste** und keine an `isPublicSitePath`.
|
|
54
|
+
- **Keine Änderung am Per-Request-Opt-out** für Bootstrap-Proben.
|
|
55
|
+
- **Keine neue Prüfung gegen Query oder Fragment.**
|
|
56
|
+
|
|
57
|
+
### Risks
|
|
58
|
+
|
|
59
|
+
- **Die Verantwortung wandert zum Konsumenten.** Ein unverankertes Muster wie `/team` trifft auch
|
|
60
|
+
`/admin/team`. Das ist der Preis der Flexibilität und muss in der Dokumentation stehen, nicht nur
|
|
61
|
+
im Kopf des Autors.
|
|
62
|
+
- **Geteilter Code**: jede konsumierende App bekommt die Änderung. Deshalb rein additiv — bestehende
|
|
63
|
+
String-Registrierungen müssen sich zeichengenau gleich verhalten.
|
|
64
|
+
- **Ein Muster, das zu eng greift, ist unsichtbar**, bis jemand die betroffene Seite anonym öffnet.
|
|
65
|
+
Deshalb gehört die Gegenrichtung mitgetestet.
|
|
66
|
+
|
|
67
|
+
### Required tests to WRITE
|
|
68
|
+
|
|
69
|
+
- **Regression, nicht verhandelbar:** String-Registrierungen verhalten sich exakt wie bisher — `/`
|
|
70
|
+
nur bei Gleichheit, andere Einträge per Präfix.
|
|
71
|
+
- Ein registriertes `RegExp` trifft die passenden Pfade; ein nicht passender Pfad **löst weiterhin
|
|
72
|
+
die Weiterleitung aus**.
|
|
73
|
+
- Das Muster wird gegen `pathname` geprüft, nicht gegen Query oder Fragment.
|
|
74
|
+
- `removePublicPath` entfernt einen `RegExp`-Eintrag; ein Builtin-Pfad lässt sich weiterhin **nicht**
|
|
75
|
+
entfernen.
|
|
76
|
+
- Ein `RegExp` in `CONSUMER_PUBLIC_PATHS` bricht die bestehende Auswertung der String-Einträge nicht.
|
|
77
|
+
- **Affected set** (laufen, nicht schreiben): die vorhandenen `apiClient`-Tests.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## B. Implementation map
|
|
82
|
+
|
|
83
|
+
**Selbst umgesetzt (nicht per Codex)** — laufende Operator-Anweisung für diese Session.
|
|
84
|
+
|
|
85
|
+
### Design-Entscheidungen
|
|
86
|
+
|
|
87
|
+
- **`addPublicPath`/`removePublicPath`** akzeptieren jetzt zusätzlich `instanceof RegExp` neben
|
|
88
|
+
`string`; `CONSUMER_PUBLIC_PATHS` bleibt ein `Set`, das für `RegExp`-Objekte automatisch
|
|
89
|
+
Identitäts-Löschung liefert (`Set.delete` nutzt SameValueZero) — kein Sonderfall nötig.
|
|
90
|
+
- **`matchesPublicPath`** prüft `entry instanceof RegExp` zuerst und ruft `entry.test(pathname)` —
|
|
91
|
+
ausschliesslich gegen `window.location.pathname`, nie gegen `search`/`hash`, da die Funktion
|
|
92
|
+
ohnehin nur `pathname` als Parameter erhält (kein Risiko einer versehentlichen Konkatenation).
|
|
93
|
+
- **`BUILTIN_PUBLIC_PATHS` unverändert** — bleibt string-only und eingefroren, keine Berührung.
|
|
94
|
+
- Dokumentation an `addPublicPath`/`removePublicPath` erweitert: Anker-Pflicht (`^…$`) für
|
|
95
|
+
`RegExp`-Muster und Identitäts-Hinweis für `removePublicPath`.
|
|
96
|
+
|
|
97
|
+
### Kontextpaket
|
|
98
|
+
|
|
99
|
+
- `src/auth/apiClient.jsx` — einzige geänderte Quelldatei.
|
|
100
|
+
|
|
101
|
+
### Tests
|
|
102
|
+
|
|
103
|
+
`tests/apiClientPublicPaths.test.js` (neu, 11 Fälle) — fährt durch den echten
|
|
104
|
+
Response-Interceptor (nicht durch eine Nachbildung von `matchesPublicPath`), damit ein Fehler in
|
|
105
|
+
der Verdrahtung selbst auffällt, nicht nur ein Fehler im Matcher. Jeder Test importiert das Modul
|
|
106
|
+
frisch (`vi.resetModules()` + dynamisches `import()`), weil `redirectingToLogin` ein
|
|
107
|
+
Modul-Level-Riegel ist, der sich in jsdom nie durch echte Navigation zurücksetzt.
|
|
108
|
+
`window.location` wird durch ein rein synthetisches Objekt ersetzt (jsdoms echtes
|
|
109
|
+
`location.assign` ist nicht konfigurierbar, und `pushState` bindet nicht an ein ersetztes
|
|
110
|
+
`location`-Objekt) — deckt exakt das ab, was der Interceptor liest (`pathname`) und aufruft
|
|
111
|
+
(`assign`). Affected set: `authApiRegistration.test.js`, `AuthContext.test.jsx`,
|
|
112
|
+
`feedApi.test.js`, `messagingApi.test.js`, `notificationsApi.test.js` — alle grün.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## C. Orchestrator only
|
|
117
|
+
|
|
118
|
+
> **STOPP — wer diesen Auftrag als eigene Spezifikation liest, ist mit diesem Abschnitt nicht
|
|
119
|
+
> gemeint.** Er beschreibt dem Orchestrator, wie er dich aufruft, wie das Ergebnis geprüft und wie es
|
|
120
|
+
> verbucht wird. **Du BIST dieser Aufruf — starte kein `codex exec`, spawne keine Reviewer, pflege
|
|
121
|
+
> das Register nicht, committe nicht.** Deine Aufgabe endet beim Diff.
|
|
122
|
+
|
|
123
|
+
### Execution directive
|
|
124
|
+
|
|
125
|
+
`.claude/codex-status.md` im Workspace-Root vor dem Aufruf prüfen. Ohne Zeile für den
|
|
126
|
+
Ausführungstag Codex verwenden — direkt per Bash, nie über die `debugger`/`*_coder`-Wrapper, mit
|
|
127
|
+
BEIDEN Flags `--skip-git-repo-check` und `--dangerously-bypass-approvals-and-sandbox`. Der Rückfall
|
|
128
|
+
auf direkte Umsetzung dreht die Autorschaft und macht den unabhängigen `reviewer` zwingend.
|
|
129
|
+
|
|
130
|
+
### Review routing
|
|
131
|
+
|
|
132
|
+
Tier 3 (shared-core): **`reviewer` und `sec_reviewer`**, gleichzeitig in einem Hintergrund-Batch vor
|
|
133
|
+
dem Commit. `ui_reviewer` entfällt — es gibt keine Oberfläche. `sec_reviewer` prüft die Kernfrage:
|
|
134
|
+
kann ein registriertes Muster die Weiterleitung dort unterdrücken, wo sie gebraucht wird, und ist die
|
|
135
|
+
Auswertung auf `pathname` beschränkt.
|
|
136
|
+
|
|
137
|
+
### Verification
|
|
138
|
+
|
|
139
|
+
Kein Prototyp, kein Zwei-Breiten-Abgleich. Der Nachweis sind die Tests, insbesondere die
|
|
140
|
+
Regressionsfälle für die String-Registrierung.
|
|
141
|
+
|
|
142
|
+
Nach grünem Review: Version anheben und veröffentlichen. **Die WO ist erst abgeschlossen, wenn die
|
|
143
|
+
Version veröffentlicht ist** — jg-ferien PUB-7 hängt daran und kann ohne sie nicht landen.
|
|
144
|
+
|
|
145
|
+
### Register + commit
|
|
146
|
+
|
|
147
|
+
Zeile `AUTH-5` erreicht `done` nur mit beiden Reviewern samt Verdikt in der `Notiz` und der
|
|
148
|
+
veröffentlichten Version. Auf grün nach `main` committen und pushen.
|
|
149
|
+
|
|
150
|
+
### Mini-handover
|
|
151
|
+
|
|
152
|
+
Repo: `ui-core-micha` (`C:\Users\biglmi\Documents\webapps\ui-core-micha`), Branch `main`.
|
|
153
|
+
WO: `work-orders/AUTH-5.md`. Konsument: `jg-ferien` PUB-7 Teil 2 (Pin heute `2.37.1`).
|
|
154
|
+
`git pull`, WO lesen, dann `orchestrate-codex` folgen.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# DX-2 — Declare `sideEffects` so consumers can tree-shake the kit
|
|
2
|
+
|
|
3
|
+
**Target repo:** `ui-core-micha` (branch `main`)
|
|
4
|
+
**Tier:** 3 — shared-core, and it changes what every consumer's bundler is permitted to discard.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## A. Envelope
|
|
9
|
+
|
|
10
|
+
### Goal
|
|
11
|
+
|
|
12
|
+
`package.json` declares **no `sideEffects` field**. Without it a bundler must assume every module in the
|
|
13
|
+
package can have an import side effect and therefore cannot drop unused exports. **Twelve consumers ship the
|
|
14
|
+
whole kit regardless of what they import.**
|
|
15
|
+
|
|
16
|
+
This is not a theory. `hram/THEME-2` switched hram from three named translation bundles to the full
|
|
17
|
+
`uiCoreTranslations` aggregate and measured the built-size delta at **−11 bytes** — not the increase everyone
|
|
18
|
+
expected, because the three-bundle import was already pulling all seven.
|
|
19
|
+
|
|
20
|
+
### Why this is worth a Tier-3 change
|
|
21
|
+
|
|
22
|
+
Two decisions currently rest on a number that this defect makes meaningless:
|
|
23
|
+
|
|
24
|
+
- **`I18N-1` measured `+28,943 B` raw / `+7,327 B` gzip** for an auth-only app switching to the aggregate.
|
|
25
|
+
That measured a real difference in *source* reachability which the build never realises. As a decision input
|
|
26
|
+
it is void.
|
|
27
|
+
- **`I18N-2` is `blocked` on exactly this.** It was framed as a bundle-size optimisation via
|
|
28
|
+
self-registration, and its stated input is a size delta that cannot be measured until the package can
|
|
29
|
+
tree-shake at all. **This WO is its unblocker**, and it is a far smaller change than reworking how twelve
|
|
30
|
+
consumers resolve strings.
|
|
31
|
+
|
|
32
|
+
### Scope
|
|
33
|
+
|
|
34
|
+
1. **Prove it first, per module.** Walk every module reachable from the package root and establish that
|
|
35
|
+
importing it has **no** side effect: no top-level statement with an observable effect, no polyfill or
|
|
36
|
+
registry mutation, no `import './x.css'` or equivalent, no module-scope singleton creation whose existence
|
|
37
|
+
something depends on. **This is the work**; the field is one line.
|
|
38
|
+
2. **`package.json`** — add `"sideEffects": false` if step 1 holds for everything. If it does **not** hold for
|
|
39
|
+
some module, add the array form listing exactly those files rather than the blanket `false`.
|
|
40
|
+
3. **Re-measure the delta `I18N-1` got wrong**: the built size of an auth-only app importing only
|
|
41
|
+
`authTranslations` versus the full aggregate, against a package that can now tree-shake. Report the number.
|
|
42
|
+
4. Update `I18N-2`'s register note with that number, so it is either authorable or droppable on evidence.
|
|
43
|
+
|
|
44
|
+
### Non-goals / do not touch
|
|
45
|
+
|
|
46
|
+
- **No `exports` map.** It would allow subpath imports and give a further size win, but it changes which
|
|
47
|
+
import paths resolve at all — a breaking-change risk for twelve consumers in the same commit. Separate
|
|
48
|
+
decision, and this WO's measurement is its input.
|
|
49
|
+
- **No component, theme, chart or i18n change.** Nothing in `src/` is edited for its own sake; the only source
|
|
50
|
+
changes permitted are ones step 1 proves are needed to make a module genuinely side-effect-free, and each
|
|
51
|
+
must be reported rather than folded in silently.
|
|
52
|
+
- **No consumer pin bumps.** Apps pick this up when they next bump.
|
|
53
|
+
- **`I18N-2` itself.** This unblocks it; it does not implement it.
|
|
54
|
+
|
|
55
|
+
### Risks
|
|
56
|
+
|
|
57
|
+
- **The real failure mode is a false `sideEffects: false`.** If a module *does* have an import side effect and
|
|
58
|
+
the field says otherwise, a bundler silently drops it and the consuming app breaks at runtime in a way no
|
|
59
|
+
test here will catch. That is why step 1 is the scope and the field is an afterthought. **If any module is
|
|
60
|
+
uncertain, use the array form and list it** — a partial declaration that is true beats a blanket one that is
|
|
61
|
+
nearly true.
|
|
62
|
+
- **`side effect` includes the theme.** `createAppTheme`, the token module and the registry are the places to
|
|
63
|
+
look hardest: a module that builds an object at import time is fine, but one that *registers* itself
|
|
64
|
+
somewhere is not.
|
|
65
|
+
- **Every consumer's bundle changes shape**, even though no source behaviour changes. That is the intent, and
|
|
66
|
+
it is why this is Tier 3 rather than a chore.
|
|
67
|
+
|
|
68
|
+
### Required tests to WRITE
|
|
69
|
+
|
|
70
|
+
- **A test asserting the field matches reality** is not really possible in-repo — a bundler is what enforces
|
|
71
|
+
it. So instead: **one build-level check** that the package builds and its entry points still resolve after
|
|
72
|
+
the declaration, plus the measurement in scope item 3 recorded as a number in the register note.
|
|
73
|
+
- If step 1 finds a module needing a source change to become side-effect-free, that change gets a test.
|
|
74
|
+
- Otherwise **no new unit tests** — and saying so is the correct answer for a `package.json` field.
|
|
75
|
+
|
|
76
|
+
Scoped run: the existing suite, to confirm nothing regressed. Not a full-estate exercise.
|
|
77
|
+
|
|
78
|
+
### Parity guardrail
|
|
79
|
+
|
|
80
|
+
No API, behaviour or visual change. Every currently-working import must keep working; what changes is only
|
|
81
|
+
what a consumer's bundler may discard.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## B. Implementation map
|
|
86
|
+
|
|
87
|
+
*Filled by the Orchestrator on `git pull` — see `AGENTS.md` → "Work Order".*
|
|
88
|
+
|
|
89
|
+
**Repo root:** `ui-core-micha` (this is the package root — `package.json` lives here, `src/index.js` is
|
|
90
|
+
the package entry per `"main"`/`"module"`). Build: `pnpm run build` (tsc, `tsconfig.build.json`,
|
|
91
|
+
`rootDir: src` → `outDir: dist`). Tests: `pnpm test` (vitest run), config likely under `tests/` or
|
|
92
|
+
`vite.config.mjs`.
|
|
93
|
+
|
|
94
|
+
**Entry point to walk:** [`src/index.js`](../src/index.js) — every export re-exported from here is a
|
|
95
|
+
module the audit must cover. Rough module groups, in the order they're exported: `theme/` (7 exports),
|
|
96
|
+
`auth/` (AuthContext, UserMenu, apiClient, authApi, authService), `layout/` (PageLayout, SectionNav),
|
|
97
|
+
`pages/` (7 page components), `components/` (12 components incl. charts subtree), `i18n/` (7 translation
|
|
98
|
+
bundle modules, pure data objects), `notifications/` (7 exports incl. a `realtime` module — check for
|
|
99
|
+
WebSocket/EventSource construction at module scope, not just component-body), `messaging/` (13 exports,
|
|
100
|
+
same realtime-connection caution), `onboarding/` (8 exports).
|
|
101
|
+
|
|
102
|
+
**Known non-trivial finding — do not silently "fix" this, report it:**
|
|
103
|
+
[`src/theme/createAppTheme.js:1-3`](../src/theme/createAppTheme.js) has three top-level
|
|
104
|
+
`import '@fontsource/dm-sans/400.css'` (+ `/500.css`, `/600.css`) — these ARE import-level side effects
|
|
105
|
+
(CSS injected into the document on import, unconditionally, regardless of whether `createAppTheme` is
|
|
106
|
+
even called). This module must be **excluded** from a blanket `sideEffects: false` — either list
|
|
107
|
+
`src/theme/createAppTheme.js` explicitly in the array form, or (better, since it's a source-level fix
|
|
108
|
+
consistent with the WO's "no false negatives" risk framing) move the three font-CSS imports into a
|
|
109
|
+
separate module (e.g. `src/theme/fonts.css.js` or inline at the one leaf that needs them) so the rest of
|
|
110
|
+
`theme/` stays tree-shakeable and only that one file is excluded. Whichever path is taken, it must be
|
|
111
|
+
**reported explicitly** in the WO's audit output, not folded in silently (Envelope: "each must be
|
|
112
|
+
reported rather than folded in silently").
|
|
113
|
+
|
|
114
|
+
**Areas most likely to hide further side effects (audit these hardest, per the Envelope's own risk
|
|
115
|
+
note):** `theme/` (registry singletons — `KIT_COMPONENT_SX_REGISTRY` is exported as a value; confirm it's
|
|
116
|
+
a plain object literal built at import with no external registration call), `notifications/realtime.js`
|
|
117
|
+
and `messaging/` (anything opening a socket/listener at module scope rather than inside a hook/effect),
|
|
118
|
+
`auth/apiClient.js` (axios instance creation is fine — a side-effect-free object build — but check for
|
|
119
|
+
any `axios.interceptors.*.use(...)` called at module scope outside a factory function, which would be a
|
|
120
|
+
real side effect since it mutates a shared axios default instance).
|
|
121
|
+
|
|
122
|
+
**Do-not-touch:** no `exports` map in `package.json` (Envelope non-goal). No component/theme/chart/i18n
|
|
123
|
+
behaviour change — only source edits that make a module genuinely side-effect-free, each reported.
|
|
124
|
+
|
|
125
|
+
**Progress contract:** emit `PLAN: …` first, then `PROGRESS: [n/total] <module-group>` before auditing
|
|
126
|
+
each group above and `… done` after, and a final `RESULT: DONE` or `RESULT: BLOCKED <reason>`. No gap
|
|
127
|
+
> ~2 min. Work from this package — open files only to verify specific claims, not a blind full-repo read.
|
|
128
|
+
|
|
129
|
+
## Preamble
|
|
130
|
+
|
|
131
|
+
The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there is
|
|
132
|
+
no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`, and
|
|
133
|
+
the app `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/deps/schema/CI
|
|
134
|
+
unless the spec says so; do not update `MEMORY.md`. **Do NOT edit `WORK_ORDERS.md`** — the register row
|
|
135
|
+
and the review verdicts are the orchestrator's alone. Do NOT `git add`/`commit`/`push` — leave every
|
|
136
|
+
change uncommitted in the working tree for the orchestrator's independent review. WRITE the tests the
|
|
137
|
+
"Required tests to WRITE" section calls for AND **RUN the tests you just wrote** to confirm they execute
|
|
138
|
+
and pass — that is the ONLY test run you do (NOT the app's affected/full suite, NOT any review). The
|
|
139
|
+
orchestrator re-runs the authoritative set + does the independent review after you finish — those are
|
|
140
|
+
the gate; your own run does not count as the gate.
|
|
141
|
+
|
|
142
|
+
Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line
|
|
143
|
+
`PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
|
|
144
|
+
completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
|
|
145
|
+
`RESULT: DONE|BLOCKED <reason>`.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## C. Orchestrator only
|
|
150
|
+
|
|
151
|
+
> **STOP — if you are the implementer reading this work order as your own specification, this section is NOT
|
|
152
|
+
> addressed to you. Skip it entirely.** It tells the Orchestrator how to invoke you. **You ARE that
|
|
153
|
+
> invocation — do NOT shell out to `codex exec`, do NOT spawn reviewers, do NOT edit `WORK_ORDERS.md`, and do
|
|
154
|
+
> NOT `git add`/`commit`/`push`.**
|
|
155
|
+
|
|
156
|
+
### Execution directive
|
|
157
|
+
|
|
158
|
+
Check `.claude/codex-status.md` first. **No line for the current date means use Codex.** A dated `unavailable`
|
|
159
|
+
line for today means skip the attempt, implement directly in Claude and name the record — that flips
|
|
160
|
+
authorship. Otherwise one probe, outcome written back either way.
|
|
161
|
+
|
|
162
|
+
### Review routing
|
|
163
|
+
|
|
164
|
+
Tier 3: **independent `reviewer`** — mandatory, and the substantive one here: ask it to **independently
|
|
165
|
+
re-check the side-effect audit**, module by module, rather than accept the implementer's list. That audit is
|
|
166
|
+
the entire safety argument. **No `ui_reviewer`** — nothing renders. No `sec_reviewer`.
|
|
167
|
+
|
|
168
|
+
### Verification
|
|
169
|
+
|
|
170
|
+
Nothing renders, so no gate. The verification is the audit plus the re-measured delta, both reported as
|
|
171
|
+
findings rather than assertions.
|
|
172
|
+
|
|
173
|
+
**A push to `main` here IS the npm publish** — `publish.yml` triggers on `push: branches: [main]` with `paths`
|
|
174
|
+
covering `package.json` and `src/**`, and npm publishes cannot be undone. So decide the version deliberately
|
|
175
|
+
before pushing: this is a **patch** by scope (no new capability, no API change), and it will go out the moment
|
|
176
|
+
it lands.
|
|
177
|
+
|
|
178
|
+
### Register + commit
|
|
179
|
+
|
|
180
|
+
One row, `DX-2`. `done` only with the independent reviewer and verdict named, plus the measured delta.
|
|
181
|
+
Update `I18N-2`'s note in the same commit — that cross-reference is half the point of this WO.
|
|
182
|
+
|
|
183
|
+
### Mini-handover
|
|
184
|
+
|
|
185
|
+
Repo: `ui-core-micha`, branch `main`. WO: `work-orders/DX-2.md`. The one-line field is trivial; **the
|
|
186
|
+
per-module side-effect audit is the work**, and a wrong `false` breaks consumers silently at runtime. Use the
|
|
187
|
+
array form if anything is uncertain. Re-measure the aggregate delta and write it into `I18N-2`. Tier 3,
|
|
188
|
+
`reviewer` mandatory and asked to redo the audit. **Pushing to main publishes.** Follow `orchestrate-codex`.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# DX-3 — `publish.yml` tests before it builds, so `2.41.2` cannot publish
|
|
2
|
+
|
|
3
|
+
**Target repo:** `ui-core-micha` (branch `main`)
|
|
4
|
+
**Tier:** 3 — CI is a Tier-3 surface regardless of diff size.
|
|
5
|
+
|
|
6
|
+
Short-form WO: two steps swapped, then an operator-confirmed dispatch.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## A. Envelope
|
|
11
|
+
|
|
12
|
+
### Goal
|
|
13
|
+
|
|
14
|
+
`DX-2` landed on `main` (`31675d8`, `e75192f`) and **its publish failed**, so `@micha.bigler/ui-core-micha`
|
|
15
|
+
is still at `2.41.1` on the registry while `package.json` says `2.41.2`. Nothing DX-2 produced can be
|
|
16
|
+
consumed until this is fixed.
|
|
17
|
+
|
|
18
|
+
Cause, from run
|
|
19
|
+
[32029216699](https://github.com/bigler-webapps/ui-core-micha/actions/runs/32029216699) —
|
|
20
|
+
`completed/failure`:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
FAIL tests/packageTreeShaking.test.js > built package entries resolve and unused translation bundles tree-shake
|
|
24
|
+
Error: ENOENT: no such file or directory, lstat '.../ui-core-micha/dist'
|
|
25
|
+
Test Files 1 failed | 64 passed (65)
|
|
26
|
+
Tests 1 failed | 510 passed (511)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`publish.yml` runs **`Run tests` before `Build`**. `DX-2`'s new test copies the package's own `dist/` to
|
|
30
|
+
assemble a fake installed consumer, and **`dist/` is gitignored** (`.gitignore:2`), so in a clean checkout
|
|
31
|
+
it does not exist yet. The test is not flaky and it is not wrong — it inspects the built artifact, which is
|
|
32
|
+
the whole point of it. **It simply cannot pass in the order the workflow runs.**
|
|
33
|
+
|
|
34
|
+
It passed locally at 511/511 because a `dist/` from an earlier `pnpm run build` was lying around. That is
|
|
35
|
+
the failure mode, not the test.
|
|
36
|
+
|
|
37
|
+
### Scope
|
|
38
|
+
|
|
39
|
+
1. **`.github/workflows/publish.yml`** — move the `Build` step **above** `Run tests`.
|
|
40
|
+
**Both steps carry `if: steps.version_check.outputs.should_publish == 'true'`; that condition must stay
|
|
41
|
+
on each.** Nothing else in the file changes.
|
|
42
|
+
2. **Publish `2.41.2`** — see the dispatch note below, which is the part most likely to be missed.
|
|
43
|
+
|
|
44
|
+
### The dispatch is required, and it is not automatic
|
|
45
|
+
|
|
46
|
+
`publish.yml` triggers on `push` to `main` filtered to `package.json`, `pnpm-lock.yaml`, `src/**` and
|
|
47
|
+
`tsconfig.build.json`. **`.github/workflows/**` is not in that list**, so landing this fix does **not**
|
|
48
|
+
re-trigger the publish. `2.41.2` would sit unpublished until some unrelated `src/**` change happened to
|
|
49
|
+
push it out.
|
|
50
|
+
|
|
51
|
+
So: after the fix lands, **dispatch `publish.yml` on `main`**. `publish.yml` is **not** on the dispatch
|
|
52
|
+
safe-allowlist (`weekly-routines`, `terraform-plan`), so per `AGENTS.md` name the exact workflow and inputs
|
|
53
|
+
and **wait for an explicit operator go** before running it.
|
|
54
|
+
|
|
55
|
+
`should_publish` compares `package.json` against the registry, so with `2.41.2` local and `2.41.1`
|
|
56
|
+
published it will evaluate true and the dispatch will publish. No version bump is needed for this WO — and
|
|
57
|
+
none should be added, because the artifact being published is DX-2's, not this WO's.
|
|
58
|
+
|
|
59
|
+
### Non-goals / do not touch
|
|
60
|
+
|
|
61
|
+
- **`tests/packageTreeShaking.test.js`.** It is correct. Do not make it build `dist/` itself, do not skip it
|
|
62
|
+
when `dist/` is absent, and above all do not delete it — a skip would silently retire the only check that
|
|
63
|
+
the `sideEffects` declaration actually tree-shakes, which is DX-2's entire deliverable.
|
|
64
|
+
- **The `sideEffects` array, `src/theme/fonts.js`, or anything else DX-2 landed.** Reviewed and correct.
|
|
65
|
+
- **The trigger `paths` list.** Adding `.github/workflows/**` would make every workflow edit attempt a
|
|
66
|
+
publish. That is a worse default than one dispatch.
|
|
67
|
+
- **The version.** Stays `2.41.2`.
|
|
68
|
+
- Any other workflow, any source file.
|
|
69
|
+
|
|
70
|
+
### Recorded, deliberately out of scope
|
|
71
|
+
|
|
72
|
+
**`publish.yml` is the only workflow in this repo that runs tests, and it runs them only when
|
|
73
|
+
`should_publish == 'true'`.** So ucm's suite executes in CI *only on a version bump* — a `src/**` change
|
|
74
|
+
that does not bump the version is never tested by CI at all. That is a real gap in a shared-core package
|
|
75
|
+
consumed by twelve apps, and it is a separate decision (a `ci.yml` for this repo, or a `pull_request`
|
|
76
|
+
trigger), not something to fold into a two-line reorder.
|
|
77
|
+
|
|
78
|
+
### Risks
|
|
79
|
+
|
|
80
|
+
- **Low, and the shape is known**: after the swap a build failure surfaces before test results instead of
|
|
81
|
+
after. That is an improvement, not a regression — you want to know the artifact is buildable before
|
|
82
|
+
spending 64 s on tests against it.
|
|
83
|
+
- **The real risk is stopping at the commit.** A landed reorder with no dispatch leaves `2.41.2`
|
|
84
|
+
unpublished and looks done. The acceptance criterion below is the registry, not the workflow file.
|
|
85
|
+
|
|
86
|
+
### Required tests to WRITE
|
|
87
|
+
|
|
88
|
+
**None.** Nothing to unit-test in a step reorder, and manufacturing one would be noise. The verification is
|
|
89
|
+
a green run plus the registry.
|
|
90
|
+
|
|
91
|
+
### Acceptance
|
|
92
|
+
|
|
93
|
+
1. A `publish.yml` run on `main` completes **green**, with `tests/packageTreeShaking.test.js` **passing**
|
|
94
|
+
rather than skipped — 511/511.
|
|
95
|
+
2. **`npm view @micha.bigler/ui-core-micha version` returns `2.41.2`.** The registry is the acceptance,
|
|
96
|
+
not the commit and not the green tile.
|
|
97
|
+
|
|
98
|
+
### Parity guardrail
|
|
99
|
+
|
|
100
|
+
No source, API, behaviour or visual change. What changes is the order two CI steps run in.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## B. Implementation map
|
|
105
|
+
|
|
106
|
+
*Filled by the Orchestrator on `git pull` — see `AGENTS.md` → "Work Order".*
|
|
107
|
+
|
|
108
|
+
**Repo root:** `ui-core-micha` (workflow lives at repo root, `.github/workflows/publish.yml`).
|
|
109
|
+
|
|
110
|
+
**Exact change:** in [`.github/workflows/publish.yml`](../.github/workflows/publish.yml), the step
|
|
111
|
+
order today (verified on `git pull`, lines 112-118) is:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
- name: Run tests
|
|
115
|
+
if: steps.version_check.outputs.should_publish == 'true'
|
|
116
|
+
run: pnpm run test
|
|
117
|
+
|
|
118
|
+
- name: Build
|
|
119
|
+
if: steps.version_check.outputs.should_publish == 'true'
|
|
120
|
+
run: pnpm run build
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Swap so `Build` runs first, `Run tests` second — same two `if:` conditions, unchanged, one on each
|
|
124
|
+
step. Nothing else in the file (`Check out code`, `Set up Node`, `Decide whether to publish`,
|
|
125
|
+
`Set up pnpm`, `Show version from package.json`, `Install dependencies (pnpm)`,
|
|
126
|
+
`Ensure recent npm for trusted publishing`, `Publish to npm`) moves or changes.
|
|
127
|
+
|
|
128
|
+
**Do-not-touch (repeat of the Envelope's non-goals, stated here for the implementer):**
|
|
129
|
+
`tests/packageTreeShaking.test.js`, the `sideEffects` array / `src/theme/fonts.js` (DX-2, already
|
|
130
|
+
landed and reviewed), the `on: push: paths` trigger list, `package.json`'s version (stays `2.41.2`).
|
|
131
|
+
This WO touches exactly one file.
|
|
132
|
+
|
|
133
|
+
**Progress contract:** `PLAN: …` naming the single edit, `PROGRESS: [1/1] swap Build/Run-tests order
|
|
134
|
+
in publish.yml … done`, then `RESULT: DONE`.
|
|
135
|
+
|
|
136
|
+
## Preamble
|
|
137
|
+
|
|
138
|
+
The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there is
|
|
139
|
+
no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`, and
|
|
140
|
+
the app `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/deps/schema/CI
|
|
141
|
+
beyond the one named swap. Do not update `MEMORY.md`. **Do NOT edit `WORK_ORDERS.md`** — the register
|
|
142
|
+
row and the review verdicts are the orchestrator's alone. Do NOT `git add`/`commit`/`push`, and do NOT
|
|
143
|
+
dispatch any workflow — leave every change uncommitted in the working tree for the orchestrator's
|
|
144
|
+
independent review. This WO's "Required tests to WRITE" is explicitly **none** — do not write or run
|
|
145
|
+
any test; there is nothing to verify locally that the orchestrator's own read of the diff and the
|
|
146
|
+
reviewer's pass don't already cover.
|
|
147
|
+
|
|
148
|
+
Narrate continuously: a `PLAN: <step1> | …` line up front, then a single-line
|
|
149
|
+
`PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
|
|
150
|
+
completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
|
|
151
|
+
`RESULT: DONE|BLOCKED <reason>`.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## C. Orchestrator only
|
|
156
|
+
|
|
157
|
+
> **STOP — if you are the implementer reading this work order as your own specification, this section is NOT
|
|
158
|
+
> addressed to you. Skip it entirely.** It tells the Orchestrator how to invoke you. **You ARE that
|
|
159
|
+
> invocation — do NOT shell out to `codex exec`, do NOT spawn reviewers, do NOT edit `WORK_ORDERS.md`, do
|
|
160
|
+
> NOT dispatch any workflow, and do NOT `git add`/`commit`/`push`.**
|
|
161
|
+
|
|
162
|
+
### Execution directive
|
|
163
|
+
|
|
164
|
+
Check `.claude/codex-status.md` first. **No line for the current date means use Codex.** A dated
|
|
165
|
+
`unavailable` line for today means skip the attempt, implement directly in Claude and name the record —
|
|
166
|
+
that flips authorship, which keeps the independent `reviewer` mandatory. Otherwise one probe, outcome
|
|
167
|
+
written back either way.
|
|
168
|
+
|
|
169
|
+
### Review routing
|
|
170
|
+
|
|
171
|
+
Tier 3: **independent `reviewer`** — mandatory even for two lines, because CI is the surface. **No
|
|
172
|
+
`ui_reviewer`** (nothing renders), no `sec_reviewer`. Ask the reviewer to confirm the `if:` conditions
|
|
173
|
+
survived the move on **both** steps — dropping one would make a step run unconditionally, which is the only
|
|
174
|
+
way a two-line reorder can go wrong.
|
|
175
|
+
|
|
176
|
+
### Verification
|
|
177
|
+
|
|
178
|
+
Acceptance criterion 2 is the verification: **read the registry.** A green tile is not a publish, and this
|
|
179
|
+
session has now had the publish status misreported in both directions on the same package — once assumed
|
|
180
|
+
published when it was not, once assumed unpublished when it was. `npm view`, every time.
|
|
181
|
+
|
|
182
|
+
### Register + commit
|
|
183
|
+
|
|
184
|
+
One row, `DX-3`. `done` only with the reviewer's verdict named **and** the registry showing `2.41.2`. Also
|
|
185
|
+
update `DX-2`'s note: it currently reads as fully delivered, and it was not — its publish failed. Leaving
|
|
186
|
+
that uncorrected is how the next person concludes `2.41.2` is available when it is not.
|
|
187
|
+
|
|
188
|
+
### Mini-handover
|
|
189
|
+
|
|
190
|
+
Repo: `ui-core-micha`, branch `main`. WO: `work-orders/DX-3.md`. Move `Build` above `Run tests` in
|
|
191
|
+
`.github/workflows/publish.yml`, keeping the `if: should_publish` condition on both. Then **ask the operator
|
|
192
|
+
to confirm a `publish.yml` dispatch on `main`** — the workflow's own path filter excludes
|
|
193
|
+
`.github/workflows/**`, so landing the fix does not re-trigger it, and `2.41.2` stays unpublished until
|
|
194
|
+
someone dispatches. Acceptance is `npm view` returning `2.41.2`, not a green tile. Tier 3, `reviewer`
|
|
195
|
+
mandatory. Follow `orchestrate-codex`.
|