본문으로 건너뛰기
cccskills
무료GitHub에 공개

gui

Build or review ANY MeshWeaver UI — layout areas, editable views, forms, tables, content editing, side-panel composers — by wiring the framework's EXISTING data-binding pieces, NEVER hand-rolling. The one rule is ALWAYS DATA-BIND, especially editable views: the backend layout area declares WHAT to render (a UiControl tree + node PATHS), and every value read / write-back happens on the GUI side through the per-node IMeshNodeStreamCache. Hand-woven binding — replicating a node into a /data/{id} copy + a save subscription, emitting HTML strings, hand-built selects/checkboxes, .Take(1) on a live stream, async/await/QueryAsync in a layout area — is the bug class behind "awaiting first data" / "renders empty" / a stuck editable view. Use when writing or reviewing a layout area, control, editor, form, table, or any data-bound view. Grounded in src/MeshWeaver.Layout/DataBinding/, Doc/GUI/DataBinding.md + Editor.md, the Edit extension (EditorExtensions.cs), and the node-bound editor controls.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Systemorph/MeshWeaver스타 12(GitHub 12 + cccskills 0)

실행 점수 미검증

설치 방법 보기

포함된 파일(1)

  • SKILL.md14.9 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

/gui — Always data-bind. Never hand-roll. Especially editable views.

A "UI feature" in MeshWeaver means wiring up the framework's existing pieces — data binding, the controls language, the Edit editor, the node-bound editor controls, auto-persist. The framework already does rendering, two-way binding, and persistence ONE standard way that every layout area uses. Hand-rolling a parallel version — a /data copy of the node, a save subscription, an HTML string, a hand-built <select> — is FORBIDDEN and is the root of the "awaiting first data" / "renders empty" / stuck editable view bug class.

Canonical references:

  • DataBinding.md — the Golden Rule + the canonical Blazor view template.
  • Editor.md + Attributes.md — record → form via host.Edit(...).
  • src/MeshWeaver.Layout/DataBinding/ — Binding / JsonPointerReference, ReactiveExtensions (Debounce, ThrottleImmediate).
  • src/MeshWeaver.Layout/EditorExtensions.cs — the Edit macro overloads.
  • src/MeshWeaver.Graph/MeshNodeContentEditorControl.cs (ForType), src/MeshWeaver.Layout/MarkdownEditorControl.cs (WithAutoSave).
  • AGENTS.md → "🚨🚨🚨 ABSOLUTE: Never hand-roll UI / data-binding / persistence / submit"; memory feedback_no_handrolling.

The Golden Rule

The backend layout area declares WHAT to render and never fetches instances or puts concrete values into controls. Every value read of a MeshNode, and every write-back of user input, happens on the GUI side through the per-node IMeshNodeStreamCache.

// ❌ backend loads the node and bakes values in — freezes on first render, can deadlock the hub
var node = await meshQuery.QueryAsync<MeshNode>($"path:{path}").FirstOrDefaultAsync();
var card = MeshNodeThumbnailControl.FromNode(node, path);

// ✅ backend declares the binding PATH; the GUI loads + displays + stays live
var card = new MeshNodeThumbnailControl { NodePath = path };

Backend layout-area methods return UiControl (or IObservable<UiControl?> composed with Observable.Return/Select) — never async Task<UiControl>, never await, never QueryAsync, never await PermissionHelper.GetEffectivePermissions(...) (compose its IObservable<Permission> with CombineLatest). Three reasons: no hub deadlocks, live updates, and CQRS-correct authoritative reads (GetMeshNodeStream(path) ≠ the lagged read index).

Editable views — ALWAYS data binding (this is where hand-rolling sneaks in)

There are exactly three sanctioned editable surfaces. Pick by what you're editing:

You are editingUse this — it is already node-bound + auto-persisting
A plain C# record into a formhost.Edit(instance) / host.Edit(typeof(T), id) — the Editor. Reflects properties → typed field controls; [Required]/[DisplayName]/[Range]/[Browsable(false)]/[Editable(false)] drive rendering. See Editor.md / Attributes.md.
A mesh node's content (simple scalar/bool fields), data-bound + auto-persistingMeshNodeContentEditorControl.ForType(nodePath, typeof(MyContent)) — binds the GUI directly to Hub.GetMeshNodeStream(nodePath); each field writes via GetMeshNodeStream(nodePath).Update(...). ONE source of truth: the node stream.
A node's markdown / rich bodyMarkdownEditorControl.WithAutoSave(hubAddress, nodePath) or CollaborativeMarkdownControl — already node-bound, debounced auto-save.
Picking a mesh node[MeshNode("query")] → MeshNodePickerControl (stores the node PATH).

The write primitive underneath all of them is the ONE mutation API: workspace.GetMeshNodeStream(path).Update(cur => cur with { Content = … }).Subscribe(_ => {}, onError). It is COLD — the Subscribe runs the write. The owning per-node hub serialises every writer; cross-hub writes send only an RFC-7396 merge patch, so concurrent editors never clobber each other's fields.

🚨 The hand-woven tells — FORBIDDEN (these cause "awaiting first data")

If you are reaching for any of these to build a UI feature, STOP and find the control/macro above:

  1. Replicate-then-save (the #1 offender): host.UpdateData(id, node.Content) + GetDataStream(id).Debounce/Throttle.Subscribe(… GetMeshNodeStream(path).Update …) — a.k.a. any *AutoSave helper, a "Save" button that reads /data and writes the node, or a layout area that replicates a node into a /data/{id} copy. Two stores drift; the /data/content_{node} copy never seeds its initial value → the editable form binds to a stream that never emits → the area is stuck "awaiting first data" forever, fully idle (no storm, no error). This is exactly how an editable Overview hangs. The fix is to bind the editor DIRECTLY to the node stream (the table above), never a /data replica.
  2. Emitting HTML strings for structured data: StringBuilder/$"<table>…"/$"<td>…", any RenderHtml-shaped helper, or Controls.Html(handBuiltMarkup). → use a CONTROL: Controls.DataGrid(rows).WithColumn(new PropertyColumnControl<T> { Property = nameof(Row.X).ToCamelCase() }.WithTitle("…").WithFormat("N0")), composed with Controls.Stack / Controls.LayoutGrid / Controls.Title / Controls.Markdown. (Controls.Html is ONLY for genuinely pre-rendered markdown/rich text.) This is the exact hack the maintainer banned on 2026-06-19 ("use just controls and layout areas … get rid of RenderHtml … I don't want to see such hacks any more") — and the hand-rolled RenderHtml's string-interpolation + LINQ also caused the >10-min AI-project compile regression (e30e9b5f1). If you're reaching for a string of HTML, STOP and find the control.
  3. Hand-built <select>/checkbox/textarea + a /data section → the Edit macro + the attributes above, or Controls.Select / Controls.Combobox / Controls.Listbox / Controls.Text.
  4. .Take(1) on a stream feeding a live data-bound view — freezes the binding. Stay subscribed for the component's lifetime (AddBinding(...)).
  5. workspace.GetRemoteStream<MeshNode, MeshNodeReference>(addr, …) inside a Blazor view — bypasses the cache; writes through _cache.Update won't be observed. Use _cache.GetStream(path).
  6. async/await/Task<T>/.ToTask()/QueryAsync/SelectMany(async …) in a layout area — deadlocks the hub; pairs with the /async rule.
  7. A bespoke submit path for a chat message. Submitting goes through the existing hub.StartThread(...) / hub.SubmitMessage(threadPath, text, …) extensions and nothing else — no wrapper class, no path→id resolution, no create-or-submit logic beyond those APIs. Pass node PATHS through; downstream loads the node (StartThread takes a model PATH and loads the model from its mesh-node stream — don't pre-resolve an id). The full surface is in /mesh-data; it lives with the AI engine in MeshWeaver.Plugins (#2276).

Before writing ANY UI/binding/persistence code, FIND the existing framework area / control / macro / extension and use it. If you're reaching for GetDataStream / Subscribe / Update / CombineLatest / a new wrapper for a UI feature, STOP.

The controls language

Compose UiControls from the Controls factory (src/MeshWeaver.Layout/Controls.cs) — never build markup. Containers: Controls.Stack, Controls.LayoutGrid, Controls.Splitter, Controls.Tabs, Controls.Toolbar, Controls.NavMenu. Content: Controls.Markdown, Controls.Title, Controls.Text, Controls.DataGrid, charts (ChartControl). Inputs: Controls.Select, Controls.Combobox, Controls.Listbox, Controls.Text. Column API + a worked table: samples/Graph/Data/Cornerstone/Pricing/Source/PricingLayoutAreas.cs.

An activity or action page is controls, not a dump (policy action-page-controls-and-queries, proposed). Compose it from cards, grids and query controls that offer "show matching nodes". Express a SET of nodes as a mesh query (the top-level node plus scope:subtree), never as an enumerated listing pasted into a code control. The summary goes last.

Adding to the user's profile page (/{user}/EditProfile) is a section, never a new page: a compiled module registers a ProfileSectionDefinition with AddProfileSections(...); a module compiled from mesh content (the Store, for example) contributes a UiContribution node with context: "Profile", read live. A picture for any node is the platform's NodeImageUploadControl. Full reference: ProfilePage.md.

GUI side: subscribe via the cache (the canonical Blazor view)

public partial class MyView : BlazorView<MyControl, MyView>
{
    private IMeshNodeStreamCache? _cache;
    protected override void BindData()
    {
        base.BindData();
        DataBind(ViewModel.NodePath, x => x.NodePath);
        if (string.IsNullOrEmpty(NodePath)) return;
        _cache = Hub.ServiceProvider.GetRequiredService<IMeshNodeStreamCache>();
        AddBinding(_cache.GetStream(NodePath)            // shared upstream handle; access-checked
            .Where(n => n is not null).DistinctUntilChanged()
            .Subscribe(n => { Title = n.Name; InvokeAsync(StateHasChanged); }));   // NO .Take(1)
    }
    private void OnTitleChanged(string t) =>             // writes route through the SAME cache
        _cache!.Update(NodePath, c => c with { Name = t })
               .Subscribe(_ => {}, ex => Logger.LogWarning(ex, "update failed {Path}", NodePath));
}

_cache is a field (writers reuse it); AddBinding auto-disposes the subscription on teardown; _cache.GetStream terminates with UnauthorizedAccessException if the user lacks Read — handle it (toast / empty state), don't swallow. Debounce field edits with MeshWeaver.Layout.DataBinding.ReactiveExtensions.Debounce / ThrottleImmediate, not a hand-rolled timer.

Diagnose a stuck / empty editable view

"Rendering {area}... awaiting first data" (from LayoutAreaHost), a form that renders blank, or an editor that never accepts input → almost always ONE of:

  1. A hand-woven /data replica (tell #1) whose stream never seeds → bind the editor to the node stream instead (the editable-views table).
  2. The content type isn't registered in the TypeRegistry on the owning/reading hub → the content "stayed an untyped JsonElement after deserialization" and Content is T consumers get nothing. Register with config.TypeRegistry.WithType(typeof(T), nameof(T)) on the hub that reads it (and, for AI partitions, AddAITypes). Grep the logs for untyped JsonElement / not a registered type.

Both are silent (no storm, no exception, just a never-resolving binding) — confirm with a Debug-level ILogger file capture or MESHWEAVER_MSG_TRACE=1, not by raising a timeout.

Before writing ANY UI — checklist

  • Did you FIND the existing layout area / control / Edit macro / node-bound editor control and reuse it?
  • Backend area returns UiControl / IObservable<UiControl?> — no async/await/QueryAsync/Task<T>.
  • Editing a node's content? Bound DIRECTLY to the node stream (MeshNodeContentEditorControl.ForType / MarkdownEditorControl.WithAutoSave / GetMeshNodeStream(path).Update) — not a /data/{id} replica + save subscription.
  • Structured/tabular data rendered with Controls.DataGrid/Stack/LayoutGrid — no HTML strings.
  • Form fields from a record via host.Edit(...) + attributes — no hand-built selects/checkboxes.
  • GUI view reads via _cache.GetStream(path) (field, not local), writes via _cache.Update(path, fn), no .Take(1), no GetRemoteStream in the view.
  • The content type is registered in the TypeRegistry of the hub that reads it.
  • Cold write observables are .Subscribe(...)d with an onError that surfaces at a real boundary.
  • 🌍 Every string a user reads is localized — no hard-coded UI text. See below.

🌍 Every user-visible string — localize it as you write it

The portal ships English + German, so a hard-coded UI string is a bug: it renders English for every viewer regardless of their language. Localize as part of writing the view, not as a follow-up.

Covers labels, buttons, tooltips, aria-labels, placeholders, page titles, empty states, validation messages, toasts, dialog copy and menu entries — anything a human reads on screen.

// Server-side layout area
Controls.Button(host.Localize("ui.createRelease"))
stack.WithView(Controls.Body(host.Localize("ui.noCodeDefined")))

// Blazor view
@inject AccessService Access
<button title="@Access.Localize("common.close")">…</button>

// On a declaration — the translation rides next to the English, so they can't drift
[Description("Sync direction")]
[Translation("de", "Synchronisierungsrichtung")]
public SyncDirection Direction { get; init; }

Keys live in both src/MeshWeaver.Messaging.Hub/Localization/strings.{en,de}.json; LocalizationTest fails if a language is missing one, so a half-translated string cannot merge.

Prefer text that needs no translation. A language-neutral glyph beats a translated word — the AI menu's "new" entry is ➕, the node menu is ✏️ 🔖 ➡️ 📋 🗑️. Icon + translated tooltip usually beats a translated label: smaller translation surface, identical reading in every locale.

🚨 Never resolve from CultureInfo.CurrentUICulture — a layout-area render hops the hub scheduler and an ambient AsyncLocal culture doesn't survive it, so one viewer's UI would pick up another's language. Resolution is always explicit off AccessContext.Locale.

Full reference: Doc/Architecture/Localization.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

async

무료

Forwarding AccessContext through async/reactive boundaries, and the no-async rule on the actor-model mesh. Use when writing or reviewing any hub-reachable / Blazor-view / agent-round / IIoPool code that creates, reads, or updates a mesh node, OR when a write "silently does nothing" / "Access denied" appears after a .Subscribe / IIoPool / Observable.Create hop. The async/await/ToTask/.Wait/.Result/.GetAwaiter/FirstAsync family and lost AccessContext are the two ways async boundaries cause wedges and storms. Grounded in AsynchronousCalls.md, SyncedMeshNodeQueries.md, AccessContextPropagation.md.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Systemorph/MeshWeaver122026년 10월 29일 업데이트

ci

무료

What CI proves, what it cannot prove, and how to write a gate that cannot pass on no evidence. Use when authoring or changing a GitHub Actions workflow, adding a required check, wiring a satellite repo CI, or reasoning about whether a green run actually covers your change. Covers the absolute no-skip-trapdoor rule (a gate must never carry continue-on-error on its input step nor an if that asks whether a secret is set — GitHub paints a skipped job the same colour as a passed one), the reusable workflow_call jobs satellite repos must call instead of hand-rolling, why runs on main are deliberately never cancelled, and the large body of in-mesh C# that no dotnet build or test ever type-checks.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Systemorph/MeshWeaver122026년 10월 29일 업데이트

debug

무료

Debug a deadlock / hang / timeout in the mesh — a test that times out, a hub handler whose response never arrives, a GetRemoteStream / GetMeshNodeStream / GetQuery that never emits, an agent round or per-instance-hub activation that parks forever. Use whenever something times out and you need to find WHERE the message flow breaks down. The timeout is the symptom, never the bug — raising it just hangs longer. Grounded in DebuggingMessageFlow.md.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Systemorph/MeshWeaver122026년 10월 29일 업데이트

Remove / reset a user (and their partition) on a running MeshWeaver portal (AKS). Use when you need to kick out a user — e.g. a throwaway test account — so they re-onboard from scratch. The normal delete API REFUSES a user's home ("user partition root … cannot be deleted"), so deletion is a Postgres-schema drop, not an MCP call. Covers where a user's data actually lives (per-user PG schemas named by BOTH id AND email, e.g. `rbuergi` + `rbuergi@systemorph.com`), how to reach PG (the connection string is inline in the portal's `ConnectionStrings__memex` env — NOT the `POSTGRES_PASSWORD` secret), the substring-match footgun (`buergi` also matches admin `rbuergi`), the in-memory resurrection gotcha (a recycle re-saves the node — a portal restart is what actually clears it), and the half-onboarded case where the user has NO schema at all (in-memory only → restart is the whole fix).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Systemorph/MeshWeaver122026년 10월 29일 업데이트

deploy

무료

Deploy MeshWeaver — the two routes (shared AKS cluster, and Azure Container Apps via the Aspire test/prod modes) and the traps that make a deploy look done when it is not. Use when rolling a code change onto a portal, standing up or patching an environment, or verifying that what is running is what you built. FIRST RULE (maintainer, 2026-09-08): on AKS an operation is a Hosting/InstanceAction on the control instance (Roll, Restart, Suspend, Audit, Reconcile, …) executed by the in-cluster operator — az aks command invoke / kubectl are break-glass, never the procedure. Also covers the bootstrap build-image / set-image / restart sequence the operator runs for you, why an env deploy.sh is first-time setup and not a code-update path, why the database migration is a run-once Job that a helm upgrade runs — never a Deployment to roll — and the DbVersionGate that makes a portal refuse to serve ahead of its schema.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Systemorph/MeshWeaver122026년 10월 29일 업데이트

i18n

무료

Localize every user-visible string as you write it — the portal ships English + German, so a hard-coded UI string is a bug that renders English for every viewer. Use whenever you write or review text a human reads on screen: buttons, labels, tooltips, aria-labels, placeholders, page titles, empty states, validation messages, toasts, dialog copy, menu entries, settings tabs, notifications, errors. Covers the two shapes (a Translation attribute on a declaration, a strings.en/de key everywhere else), the second key home in the plugins repo whose drift guard compares VALUES and merges core-first, the CurrentUICulture ban (it covers date and number FORMATTING too), and the list of things deliberately NOT translated.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

Systemorph/MeshWeaver122026년 10월 29일 업데이트

Systemorph의 스킬 모두 보기

이 스킬의 문제 신고