From 56e4831ed4c7184bf56ca51effe5d66adc1ef9e2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 07:24:22 +0000 Subject: [PATCH 1/3] plugin-sdk docs: a plugin bundle carries no CSS of its own MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nothing in docs/plugin-sdk/ said where a plugin's styling comes from, so nothing warned that the host's Tailwind sheet is built by scanning the host's own src/** — a class written only in plugin code has no rule behind it. Named utilities survive that because the host uses the same kit; arbitrary values do not, since each is a separate rule emitted only if a host file spells it identically. Container Manager's log dialog lost h-[68vh] and max-h-[calc(100vh-13rem)] exactly this way: the log box fell back to height:auto and grew off both ends of the screen. Document the rule and the inline-style workaround where bundle authoring is already explained. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017Nz2ELEkkMQzn6N429NkFy --- docs/plugin-sdk/05-external-install.md | 35 ++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/plugin-sdk/05-external-install.md b/docs/plugin-sdk/05-external-install.md index a346e73..5965483 100644 --- a/docs/plugin-sdk/05-external-install.md +++ b/docs/plugin-sdk/05-external-install.md @@ -146,6 +146,41 @@ shasum -a 256 bundle.mjs # hoặc: python3 -c "import hashlib; print(hashlib.sha256(open('bundle.mjs','rb').read()).hexdigest())" ``` +### CSS: bundle plugin KHÔNG mang stylesheet của riêng nó + +Bundle plugin chỉ là **JavaScript** — không có file CSS nào đi kèm, và toàn +bộ giao diện ăn theo stylesheet Tailwind mà app chủ đã biên dịch sẵn. +Stylesheet đó sinh ra bằng cách quét `src/**` của **app chủ** +(`tailwind.config.js` → `content`), vốn không bao giờ chứa mã nguồn plugin +(plugin được cài lúc chạy từ một URL, không có mặt lúc app build). Hệ quả: +một class chỉ xuất hiện trong mã plugin sẽ nằm trong DOM mà **không có rule +nào phía sau** — im lặng không làm gì cả. + +- **Utility có tên** (`flex`, `p-2`, `text-xs`, `h-ctl`, token design-system): + an toàn trên thực tế — app chủ dùng chung bộ kit nên rule đã có sẵn. +- **Giá trị tuỳ ý (arbitrary value)** — `h-[68vh]`, `max-h-[70vh]`, + `grid-cols-[1fr_auto]`… — **không an toàn**: mỗi class là một rule riêng, + chỉ được phát sinh nếu tình cờ có file nào trong `src/**` của app chủ viết + y hệt từng ký tự. + +Vì vậy kích thước theo viewport hoặc theo pixel phải viết bằng inline +`style`, thứ không build step nào bỏ đi được: + +```tsx +// có thể không có rule nào trong stylesheet của app chủ +
+ +// luôn áp dụng +
+``` + +Đây không phải lo xa: dialog xem log của Container Manager đã mất đúng hai +class này, hộp log rơi về `height: auto`, phình theo từng dòng log đổ về và +đẩy dialog tràn khỏi cả trên lẫn dưới màn hình — thanh công cụ của chính nó +không với tới được. Repo plugin +(`developer-desktop-miniapp`) chặn lại bằng một test quét mã nguồn +(`ui/hostCssClasses.test.ts` trên nhánh `app/container-manager/main`). + ## Build một sidecar đúng chuẩn để publish Xem [04-tier-b-sidecars.md](./04-tier-b-sidecars.md) cho cách viết sidecar. From b3e66ffffb449d194d13b596e1a0ca7454dc6836 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 07:56:05 +0000 Subject: [PATCH 2/3] Guarantee a utility surface for runtime-installed plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A plugin bundle ships no CSS: it is built into one ESM file and styled entirely by this app's compiled Tailwind sheet. That sheet is generated by scanning src/**, and plugin source is never there — it is fetched at runtime from a URL. So a class written only in a plugin lands in the DOM with no rule behind it, silently, and "does the app happen to use this class somewhere" became an unwritten API. It had already broken all four plugins. Auditing each one against the CSS this app actually emits found bottom-3, pl-1.5, -ml-1.5, py-5, h-64, m-4, mx-5, min-h-16, min-h-20, -mt-2.5, align-top and translate-x-full all missing — while max-h-64, right beside h-64, was present. Container Manager's "Jump to latest" button had no offset and covered the first log line; its stderr rule vanished; Kafka's message preview collapsed and its resize handle overlapped the column; Redis's INFO panel lost its second column; Rabbit's scroll panes lost their padding. plugin-utilities.css uses @source inline(...) to emit the common scales — spacing, sizing, position, grid, vertical-align — whether or not this app's own code uses them, so a plugin has stable ground to stand on. It stays at general scales: colour, shadow and radius come from the design-system vocabulary, which is already used throughout src/**. Arbitrary values (max-w-[12rem], grid-cols-[minmax(...)]) can never be enumerated, so plugins must write those as inline styles; 05-external-install.md now says so, and the plugin repo checks it with scripts/check-host-classes.mjs. Costs 62 kB raw / 7 kB gzipped in a locally bundled desktop app. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017Nz2ELEkkMQzn6N429NkFy --- docs/plugin-sdk/05-external-install.md | 31 +++++++++++++--- src/styles/globals.css | 2 ++ src/styles/plugin-utilities.css | 49 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/styles/plugin-utilities.css diff --git a/docs/plugin-sdk/05-external-install.md b/docs/plugin-sdk/05-external-install.md index 5965483..981bf72 100644 --- a/docs/plugin-sdk/05-external-install.md +++ b/docs/plugin-sdk/05-external-install.md @@ -174,12 +174,33 @@ Vì vậy kích thước theo viewport hoặc theo pixel phải viết bằng in
``` +**Utility có tên cũng không miễn nhiễm.** Rà soát cả bốn plugin bằng đúng +file CSS app chủ biên dịch ra cho thấy `bottom-3`, `pl-1.5`, `-ml-1.5`, +`py-5`, `h-64`, `m-4`, `mx-5`, `min-h-16`, `min-h-20`, `-mt-2.5`, +`align-top`, `translate-x-full` đều KHÔNG có rule — trong khi `max-h-64` nằm +ngay cạnh `h-64` thì có. Vì thế `src/styles/plugin-utilities.css` dùng +`@source inline(...)` ép Tailwind phát sinh sẵn các thang đo chung (spacing, +kích thước, vị trí, lưới, canh dọc) cho plugin dựa vào, dù không file nào +trong `src/**` dùng tới. + +Safelist đó chỉ có ở bản app chủ từ đây trở đi. **Plugin cài vào bản app chủ +nào người dùng đang có**, nên mã plugin vẫn phải chạy đúng trên bản cũ — và +không safelist nào phủ nổi giá trị tuỳ ý. Repo plugin +(`developer-desktop-miniapp`) kiểm bằng `scripts/check-host-classes.mjs`: +nó phân giải mọi class plugin viết ra dựa trên một file CSS app chủ thật, +chạy với bản phát hành cũ nhất còn hỗ trợ. + +```bash +# trong checkout app chủ +npm run build +# trong repo plugin +node scripts/check-host-classes.mjs ../developer-desktop-utils/dist/assets/*.css +``` + Đây không phải lo xa: dialog xem log của Container Manager đã mất đúng hai -class này, hộp log rơi về `height: auto`, phình theo từng dòng log đổ về và -đẩy dialog tràn khỏi cả trên lẫn dưới màn hình — thanh công cụ của chính nó -không với tới được. Repo plugin -(`developer-desktop-miniapp`) chặn lại bằng một test quét mã nguồn -(`ui/hostCssClasses.test.ts` trên nhánh `app/container-manager/main`). +class kích thước, hộp log rơi về `height: auto`, phình theo từng dòng log đổ +về và đẩy dialog tràn khỏi cả trên lẫn dưới màn hình — thanh công cụ của +chính nó không với tới được. ## Build một sidecar đúng chuẩn để publish diff --git a/src/styles/globals.css b/src/styles/globals.css index e702465..86a5bff 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -2,6 +2,8 @@ @import "../design-system/tokens.css"; @import "tailwindcss"; +/* Thang utility bảo đảm cho plugin cài lúc chạy — xem file đó. */ +@import "./plugin-utilities.css"; /* Keep the JS config as the source of truth for now: it carries the portable design-kit preset chain (design/tailwind-preset.cjs), the colour vocabulary and the animate plugin. Migrating it to CSS `@theme` is a separate step. */ diff --git a/src/styles/plugin-utilities.css b/src/styles/plugin-utilities.css new file mode 100644 index 0000000..62c4d5b --- /dev/null +++ b/src/styles/plugin-utilities.css @@ -0,0 +1,49 @@ +/* ============================================================================ + Bề mặt utility bảo đảm cho plugin cài từ bên ngoài + ---------------------------------------------------------------------------- + Plugin (Settings → Extensions) được build thành MỘT file ESM và **không mang + CSS của riêng nó** — xem `docs/plugin-sdk/05-external-install.md`. Toàn bộ + giao diện plugin ăn theo stylesheet này, vốn sinh ra bằng cách quét `src/**` + của app chủ. Mã nguồn plugin không có mặt lúc app build (nó được tải lúc + chạy từ một URL), nên trước file này, một class chỉ có trong plugin sẽ nằm + trong DOM mà **không có rule nào phía sau** — im lặng không làm gì cả. + + Điều đó biến "app chủ có tình cờ dùng class này ở đâu đó không" thành hợp + đồng API ngầm, và nó đã gãy thật: Container Manager mất `bottom-3` (nút + "Jump to latest" rơi lên đỉnh khung log), `pl-1.5`/`-ml-1.5`/`border-bad/60` + (vạch đỏ đánh dấu dòng stderr biến mất); Kafka Explorer mất `h-64`, `m-4`, + `py-5`, `translate-x-full`; Redis mất `min-h-16`, `mx-5`, `align-top`; + RabbitMQ mất `py-5`, `min-h-16`, `min-h-20`, `-mt-2.5`. + + `@source inline(...)` buộc Tailwind phát sinh các thang đo dưới đây dù không + file nào trong `src/**` dùng tới, nên plugin có một nền ổn định để dựa vào. + Giữ danh sách ở mức THANG ĐO CHUNG (khoảng cách, kích thước, vị trí) — + những thứ một layout bất kỳ cũng cần. Không thêm class trang trí vào đây: + màu, bóng, bo góc của plugin phải đến từ từ vựng design-system, thứ app chủ + vốn đã dùng khắp nơi. + + KHÔNG thể bảo đảm giá trị tuỳ ý (`max-w-[12rem]`, `grid-cols-[minmax(...)]`) + — mỗi cái là một rule riêng, không thang đo nào liệt kê hết được. Plugin + phải viết chúng bằng inline `style`. + ========================================================================== */ + +/* Thang spacing: margin/padding mọi phía, gap, và bù âm cho margin. */ +@source inline("{,-}m{,x,y,s,e,t,r,b,l}-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16,20,24}"); +@source inline("p{,x,y,s,e,t,r,b,l}-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16,20,24}"); +@source inline("gap{,-x,-y}-{0,0.5,1,1.5,2,2.5,3,3.5,4,5,6,8,10,12}"); + +/* Thang kích thước + các từ khoá nội tại hay dùng để dựng pane cuộn. */ +@source inline("{w,h,size,min-w,min-h,max-w,max-h}-{0,px,0.5,1,1.5,2,2.5,3,3.5,4,5,6,7,8,9,10,11,12,14,16,20,24,28,32,36,40,44,48,56,64,72,80,96}"); +@source inline("{w,h,min-w,min-h,max-w,max-h}-{full,screen,fit,min,max,none,auto}"); + +/* Vị trí cho overlay/badge/nút nổi — chỗ `bottom-3` đã gãy. */ +@source inline("{,-}{inset,inset-x,inset-y,top,right,bottom,left,start,end}-{0,px,0.5,1,1.5,2,2.5,3,3.5,4,5,6,8,10,12,full,auto}"); +@source inline("{,-}{inset,inset-x,inset-y,top,right,bottom,left}-{1/2,1/3,2/3,1/4,3/4}"); + +/* Biến đổi dùng cho panel trượt và canh giữa tuyệt đối. */ +@source inline("{,-}translate-{x,y}-{0,px,0.5,1,1.5,2,2.5,3,4,6,8,full,1/2}"); + +/* Lưới và canh dọc — `grid-cols-N` tuỳ ý và `align-top` đều từng thiếu. */ +@source inline("grid-{cols,rows}-{1,2,3,4,5,6,7,8,9,10,11,12,none}"); +@source inline("col-span-{1,2,3,4,5,6,full}"); +@source inline("align-{baseline,top,middle,bottom,text-top,text-bottom,sub,super}"); From 74c4b6c375a090c36e493877065e2c95d8bb9f68 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 22 Sep 2026 08:28:00 +0000 Subject: [PATCH 3/3] plugin-utilities.css: say how this relates to the safelist that predates it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/styles/externalPluginClassnamesSafelist.ts already existed and already solved this problem — a snapshot of plugin class names extracted once when the four tools left this repo (1b59500, 5ad6ba2), kept purely as scannable text. I added a second mechanism without saying how the two relate, which would leave a reviewer guessing. The snapshot is not broken; it is stale. Its own comment predicted this: "no automated re-sync". Plugin code moved on after that extraction, and nothing re-syncs it — which is exactly why bottom-3, py-5, align-top, translate-x-full and min-h-16 had no rule, while max-w-[9rem] and grid-cols-[3rem_minmax(0,1fr)_6.5rem_5.5rem], both in the snapshot, did. @source inline() addresses precisely that weakness for the general scales: it enumerates by scale rather than by usage, so it needs no plugin checkout and cannot go stale. It does not replace the snapshot — arbitrary values can only ever come from there, and so remain able to go stale, which is why plugins must write those as inline styles. Both files now say this, as does 05-external-install.md. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017Nz2ELEkkMQzn6N429NkFy --- docs/plugin-sdk/05-external-install.md | 12 +++++++++++- src/styles/plugin-utilities.css | 22 +++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/docs/plugin-sdk/05-external-install.md b/docs/plugin-sdk/05-external-install.md index 981bf72..eb2f692 100644 --- a/docs/plugin-sdk/05-external-install.md +++ b/docs/plugin-sdk/05-external-install.md @@ -174,6 +174,13 @@ Vì vậy kích thước theo viewport hoặc theo pixel phải viết bằng in
``` +**Đã có một cơ chế cho việc này từ trước:** +`src/styles/externalPluginClassnamesSafelist.ts` — bản chụp class trích ra MỘT +LẦN lúc bốn tool tách khỏi repo app chủ, tồn tại chỉ để Tailwind quét thấy. Nó +không hỏng, nó **cũ**: chính comment của nó đã báo trước "no automated +re-sync", và mã plugin đi tiếp sau ngày đó. Nếu bạn thêm class mới vào một +plugin, đừng cho rằng nó đã được phủ. + **Utility có tên cũng không miễn nhiễm.** Rà soát cả bốn plugin bằng đúng file CSS app chủ biên dịch ra cho thấy `bottom-3`, `pl-1.5`, `-ml-1.5`, `py-5`, `h-64`, `m-4`, `mx-5`, `min-h-16`, `min-h-20`, `-mt-2.5`, @@ -181,7 +188,10 @@ file CSS app chủ biên dịch ra cho thấy `bottom-3`, `pl-1.5`, `-ml-1.5`, ngay cạnh `h-64` thì có. Vì thế `src/styles/plugin-utilities.css` dùng `@source inline(...)` ép Tailwind phát sinh sẵn các thang đo chung (spacing, kích thước, vị trí, lưới, canh dọc) cho plugin dựa vào, dù không file nào -trong `src/**` dùng tới. +trong `src/**` dùng tới. Nó liệt kê theo THANG ĐO chứ không theo cách dùng, +nên không cần đọc mã plugin và không cũ đi được — bù đúng điểm yếu của bản +chụp. Hai file bổ sung nhau: bản chụp vẫn là thứ duy nhất phủ được các giá trị +tuỳ ý plugin đang dùng. Safelist đó chỉ có ở bản app chủ từ đây trở đi. **Plugin cài vào bản app chủ nào người dùng đang có**, nên mã plugin vẫn phải chạy đúng trên bản cũ — và diff --git a/src/styles/plugin-utilities.css b/src/styles/plugin-utilities.css index 62c4d5b..f1c63d5 100644 --- a/src/styles/plugin-utilities.css +++ b/src/styles/plugin-utilities.css @@ -15,16 +15,28 @@ `py-5`, `translate-x-full`; Redis mất `min-h-16`, `mx-5`, `align-top`; RabbitMQ mất `py-5`, `min-h-16`, `min-h-20`, `-mt-2.5`. - `@source inline(...)` buộc Tailwind phát sinh các thang đo dưới đây dù không - file nào trong `src/**` dùng tới, nên plugin có một nền ổn định để dựa vào. + ĐÃ CÓ MỘT CƠ CHẾ TRƯỚC FILE NÀY: `externalPluginClassnamesSafelist.ts` — + một bản chụp class được trích ra MỘT LẦN lúc bốn tool tách khỏi repo này + (commit 1b59500 / 5ad6ba2), tồn tại chỉ để Tailwind quét thấy. Nó không hỏng; + nó CŨ. Chính comment của nó đã báo trước: "no automated re-sync". Mã plugin + đi tiếp sau ngày đó, thêm class mới, và không có gì đồng bộ lại — đó là lý do + chính xác vì sao `bottom-3`, `py-5`, `align-top`, `translate-x-full`, + `min-h-16` không có rule, trong khi `max-w-[9rem]` hay + `grid-cols-[3rem_minmax(0,1fr)_6.5rem_5.5rem]` (có trong bản chụp) thì có. + + `@source inline(...)` dưới đây bù đúng điểm yếu đó cho phần THANG ĐO CHUNG: + nó liệt kê theo thang, không theo cách dùng, nên không cần đọc mã plugin và + không bao giờ cũ đi. Hai file bổ sung nhau, không thay thế nhau — bản chụp + kia vẫn là thứ duy nhất phủ được các giá trị tuỳ ý plugin đang dùng. Giữ danh sách ở mức THANG ĐO CHUNG (khoảng cách, kích thước, vị trí) — những thứ một layout bất kỳ cũng cần. Không thêm class trang trí vào đây: màu, bóng, bo góc của plugin phải đến từ từ vựng design-system, thứ app chủ vốn đã dùng khắp nơi. - KHÔNG thể bảo đảm giá trị tuỳ ý (`max-w-[12rem]`, `grid-cols-[minmax(...)]`) - — mỗi cái là một rule riêng, không thang đo nào liệt kê hết được. Plugin - phải viết chúng bằng inline `style`. + KHÔNG thang đo nào liệt kê nổi giá trị tuỳ ý (`max-w-[12rem]`, + `grid-cols-[minmax(...)]`) — mỗi cái là một rule riêng. Chúng vẫn phụ thuộc + bản chụp kia, tức vẫn cũ đi được; nên plugin phải viết chúng bằng inline + `style`, và repo plugin có test chặn lại (`ui/hostCssClasses.test.ts`). ========================================================================== */ /* Thang spacing: margin/padding mọi phía, gap, và bù âm cho margin. */