Search/Maintain a Multi-Platform UI Library Without Breaking Its Package Layers

Maintain a Multi-Platform UI Library Without Breaking Its Package Layers

For a UI library split into layered packages for several platforms: the AI keeps package layers and public exports intact, builds provider adapters in a standard layout, and follows the release and review rules.

To use this AI rule
## Decide Which Package Layer Each Piece of Code Belongs In Organize the codebase as a small stack of layers, each with one responsibility: reactive primitives that run framework hooks as headless resources; a binding layer that bridges those primitives to the framework's state system; a framework-agnostic core runtime that owns the domain logic; and one distribution package per target platform (web, native, terminal) that re-exports the core and adds platform-specific UI primitives. A private component kit holds the canonical UI source that is copied into consumer projects rather than published as a runtime dependency. Shared build tooling lives in its own internal package. Placement rules: - Framework-agnostic logic belongs in the core package. Platform-coupled runtimes belong in the distribution packages. - The framework-coupled runtime lives in a sub-path of the core package that every distribution re-exports, so there is one implementation instead of one copy per platform. - Consumers never install the core package directly; they install exactly one distribution package. - Integration packages that map an external provider onto the core runtime are their own layer and never fork core logic. ## Add Public Exports Only, Never Remove Shipped Ones The public surface of every published package is append-only. When a symbol moves, re-point its export to the new file; never delete an export that has already shipped. An in-repository audit cannot see downstream consumers, so removing even an apparently unused type is a breaking change. Ship a real behavior change as its own deliberate change, never smuggled inside a move or rename. When a long migration replaces a legacy implementation with a new architecture, the package entry point re-exports both paths for the duration and still stays append-only. During such a migration: - Add new behavior on the canonical surface, never on the deprecated path. - Reuse the shared core primitive instead of writing a per-integration copy of it. - Prefer reuse over introducing yet another opt-out flag. ## Build a Provider Adapter With the Standard Set of Files An integration adapter maps an external provider onto the core runtime through one entry hook, a set of accessor hooks, and pure converters. The governing rule of thumb: the runtime file orchestrates, pure modules convert, the controller (when there is one) reduces, and hooks only read. - Core runtime choice. Build either on the external-store runtime, when messages are derived from a source the provider owns, or on the local runtime plus a chat-model adapter, when the provider holds no thread state. Wrap either in the shared multi-thread runtime when thread lists are needed. Reuse the shipped runtime cores; do not hand-roll a parallel thread-runtime state holder. - State exposure. Expose runtime state to accessor hooks through the shared runtime-extras factory, not a hand-rolled unique-symbol brand and guard. - Standard file set. One orchestration-only runtime entry file, one runtime-extras instance, one accessor-and-action hooks file, one pure bidirectional message-converter file, and one types file. Add a thread controller plus a pure state reducer when the adapter owns thread state, and a separate server or node sub-path entry when the protocol owns the wire format. - Server-only code. Keep server-only and provider-SDK code inside the server or node sub-path, out of the default entry and out of the native entry. - Tests. Colocate them beside each module, covering the converter in both directions, the reducer or controller, and every accessor hook. Provider-driven choices stay flexible: which core primitive to build on, thin wrapper versus accumulator versus controller, bespoke transports, how rich human-in-the-loop support is, and how deep thread-list support goes. ## Give Every Adapter the Same Features and Crash-Free Converters Cross-runtime parity. Keep capabilities at parity across adapters. When a capability lands on one runtime, expose it on every runtime where the concept applies, or write down why one diverges. A silent parity gap becomes a breaking expectation for anyone switching adapters. Defensive converters. Converters and content-block renderers must never throw on undefined fields, missing fields, or unavailable platform capabilities. Defend against off-specification provider payloads, and keep browser-only platform APIs out of code paths that also run on a server, in a terminal renderer, or on a native target. Patterns never to introduce: - A parallel thread-runtime state holder that duplicates the shipped runtime cores. - A change-notification plus version-counter re-render hack for bridging non-reactive state into the component tree. - Grafting methods onto objects through prototype creation. - Monkeypatching objects owned by the caller. ## Build With the Shared Build Tool and Add a Release Note Every publishable package builds through the shared build tool. Do not add a per-package build configuration or reach for an alternative bundler or the compiler CLI directly. Export maps are module-only and types-first, with type declarations listed ahead of the default condition, and packages declare themselves side-effect free. - One canonical UI source directory. Templates and examples alias it through compiler path mappings and carry no byte-identical duplicates, except where a minimal starter deliberately ships its own copy. A sync script keeps that copy byte-identical, and every intentional divergence is declared in the script's overrides list. - Directory-shape conventions differ by intent: parallel directories when every primitive exists in both flavors, and sparse variant suffixes when only some files have a second flavor, with the unsuffixed file as the default source. - Re-run the compiler-plugin verification task when bumping the compiler toolchain or its plugins. A green build alone does not prove the toolchain is still intact. - Any package the published bundle imports at runtime belongs in runtime dependencies, not development dependencies, so the bundler treats it as external. Misplacing it inlines the package and drags unresolvable transitive imports into consumer builds. - A copy-installable component entry must be self-contained: enumerate every internal component import and stylesheet import as a registry dependency so the installer never lands a file with an unresolvable import. - Every change to a published package carries a release note entry. Always use a patch bump; minor and major bumps require maintainer approval. Private packages are exempt. ## Leave the Generated API Reference to Its Generator Script The entire generated API-reference tree is owned by its generator script, not by hand. - Pages carrying the full-page generated marker are wholly generator-owned. Hand edits inside a generated block are destroyed on the next run; durable prose belongs in the source doc comments instead. - Hand-written content that must survive regeneration goes only in the designated manual or example slots. - A fully hand-maintained page must carry the skip-auto-generation marker immediately after its frontmatter. That marker keeps the page listed in the section navigation and exempts it from pruning; without it, an unexpected non-generated page is reported as unmanaged and stale, and strict mode fails. - Section and root navigation metadata files under that tree are generated too. Never hand-edit them. - Continuous integration regenerates in strict mode and fails on any drift, so regenerate locally before submitting. ## Let the Linter and Formatter Handle Style, and Keep Comments Minimal Run the repository's linter, and its autofix mode for mechanical issues. The formatter owns formatting; never hand-format, and never raise spacing or formatting nits in review. Because reactive resources are built on framework hooks, dependency-array and hook-order rules are enforced by the linter's native hook rules. For those rules to analyze a body, the function must be named so the framework's tooling recognizes it as a hook: extract a resource into a hook-prefixed function and pass that to the resource factory, and give the root-creation helpers a named function expression rather than an arrow function. Default to zero code comments. Delete any comment that restates the code, records its history, or points at the current change request. Keep a comment only when it documents a reason no future reader could recover from the code itself, such as a hidden invariant, a non-obvious constraint, or an upstream workaround, and write it as a neutral declarative sentence. ## Put Tests Next to Each Module and Skip Snapshot Assertions Tests live beside the module under test and import it by relative path, never through the package name, so a test always exercises the local source rather than a resolved build output. - Cover the converter in both directions, the reducer or controller, and each accessor hook in its own test file. - Build mocks with the runner's hoisted-mock helper, and always spread the original module into the mock so untouched exports keep their real behavior. - Do not use snapshot assertions; assert on the specific values that matter, so a failure names the actual regression. ## Submit One Focused Change With a Clear Description and Reproduction - One concern per change request. Break a large or multi-purpose change into separate submissions so each is small enough to review, can be approved on its own merits, and reverts cleanly when it regresses. - Write a real description, not a generated placeholder. State what the change is, why it is needed, and how it was done, so a reviewer has enough context without reverse-engineering the diff. The title names the change in one line; trade-offs and divergences belong in the body. - Justify any divergence from existing repository practice. If the codebase already solves a problem one way, follow it; someone chose that approach deliberately, so a different one needs a written reason rather than personal preference. - Keep unrelated changes out of the diff. Drive-by deletions, refactors, reformatting, and while-I-am-here additions bury the real change and slow review. Open a separate submission, or an issue when the point is only to flag something. - Open an issue before a non-trivial feature change. One paragraph lets a maintainer confirm the direction is wanted before significant time is spent. Trivial fixes such as typos or small documentation edits need no issue. - Attach a minimal reproduction. Every defect report or fix needs a runnable reproduction on the exact version. If a maintainer cannot reproduce the reported behavior, the report is closed. - Confirm the fix addresses the root cause. Verify that the reported failure disappears with the change and returns without it, and explain the mechanism. A change that masks the symptom by swapping a value, dropping a feature, or widening a type is rejected. - Keep documentation and generated reference in lockstep with code in the same submission. Regenerate the reference and update every page whose claims the change invalidates. Automation should fail on documentation drift rather than let an API silently disappear or be deferred to a later change. ## Avoid Changes Reviewers Reject, Like Reinventing Framework Features - Reinventing what the framework already provides. Build on the shared primitives instead of re-implementing runtime state, reactivity, or other plumbing; a parallel mechanism drifts from the rest of the codebase and costs everyone to maintain. - Adding heavy or non-open-source dependencies casually. Commercially licensed libraries are off-limits in an open-source project, and heavy dependencies or new build steps affect every contributor, so they need justification and maintainer sign-off. - Shipping a new example application that duplicates an existing one. Scan the existing examples first; if one already covers the integration, a near-duplicate adds no ground and belongs in a separate personal repository unless a maintainer asked for it. - Fixing a defect by introducing a user-experience regression. Disabling a feature, dropping an animation, or widening an interface to mask a rendering or jitter defect is not an acceptable fix, even when the original report is real. - Adding defensive checks the toolchain already enforces. Under the strictest compiler settings, including exact optional-property checking, nullability and optional-property guards are redundant. - Overriding required checks to merge. Do not force a merge until the check listing shows every row passing or explicitly skipped; filter out the passing and skipped rows and confirm nothing remains, because a truncated listing once hid a failing check. When a repository-specific check is failing or pending, resolve it instead of overriding it. ## Avoid Overwriting Other Agents' Changes in the Same Worktree Assume other automated agents and humans are editing the same worktree at the same time. Before editing, inspect the current worktree state and avoid overwriting changes you did not make. Keep each change tightly scoped so concurrent work can be separated cleanly. Respect the project's version-control workflow rather than assuming raw version-control commands are appropriate. When the working branch indicates the project is managed through a workflow tool layered over version control, do not create branches, stage files, commit, or rewrite history with direct commands unless explicitly asked to.