@
feat(ui): add DataTable and Toaster, drop the legacy Table, fix overlay dialogs DataTable replaces the 20-line Table scaffold entirely: columns, sorting, filtering, pagination, selection, bulk actions, comparison layout, sticky first column, custom HTML cells, and a remote source driven by a `request` output rather than a function prop (props travel as HTML attributes, so a function arrives as its own source text). Toaster replaces the hand-rolled status div: tone icons, actions, hover pause/resume and a progress bar. Overlays audit -- Modal and Drawer declared aria-modal="true" but nothing ever moved focus into the panel, so the @keydown handler on their root never ran and closeOnEscape did nothing. Focus, focus restore, a Tab trap and a body scroll lock now live in the reactive runtime, shared by both. ContextMenu placed pointer menus by subtracting a guessed 340x420 from the viewport, which pushed every menu that was not that size away from the pointer; it now positions at the pointer and lets the anchored clamp pull it back once it can be measured. The reactive runtime size budget moves 150k -> 175k to cover anchored overlays, dialog behaviour, the toaster and the DataTable client half. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> @
This commit is contained in:
@@ -0,0 +1,500 @@
|
||||
# WRNexusJS — Security & Power Improvement Plan
|
||||
|
||||
Prepared for WorkRoot · covers `E:\WireJS` (framework v0.8.4)
|
||||
|
||||
This plan is scoped and sequenced the way the repo's own roadmap docs are (`ROADMAP-V1.md`,
|
||||
`IMPLEMENTATION-ROADMAP-0.8.md`): version-gated phases, one package/file set per item, with
|
||||
implementation, tests, and doc updates called out per item so each phase can ship as a real release
|
||||
with a `bun run validate:0.X` gate, like 0.7 and 0.8 did.
|
||||
|
||||
Every proposed API below follows conventions already in the repo (workspace `package.json` shape,
|
||||
`Middleware`/`Context` typing from `@wrnexus/core`, ASVS row format in `docs/SECURITY-ASVS-5.md`,
|
||||
audit-sink pattern from `packages/authz/src/audit.ts`) rather than inventing new patterns.
|
||||
|
||||
---
|
||||
|
||||
## Phasing overview
|
||||
|
||||
| Phase | Version | Theme | New packages | Est. effort |
|
||||
| ----- | ------------- | ----------------------------------------------- | ---------------------------------------------- | ----------- |
|
||||
| 1 | 0.8.5 (patch) | Security default fixes, no breaking changes | none | 1–2 weeks |
|
||||
| 2 | 0.9 | Distributed rate limiting + live security audit | `@wrnexus/ratelimit-redis` | 2–3 weeks |
|
||||
| 3 | 0.10 | Product-critical DX packages | `@wrnexus/mail`, `@wrnexus/flags` | 4–6 weeks |
|
||||
| 4 | 0.11 | Search + AI pairing | `@wrnexus/search` | 3–4 weeks |
|
||||
| 5 | 0.12 | Enterprise/government readiness | SAML in `@wrnexus/auth`, `@wrnexus/compliance` | 5–7 weeks |
|
||||
| 6 | 1.0 | Monetization + ecosystem | `@wrnexus/billing`, public release strategy | 6–10 weeks |
|
||||
|
||||
Total: roughly 6–8 months at a small-team pace, phased so each release is independently shippable
|
||||
and dogfoodable on `workroot.in` / `wrnexusjs.dev` / the WRNexus SaaS itself before the next phase
|
||||
starts.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — v0.8.5: Security default fixes
|
||||
|
||||
No new packages. Pure hardening of existing code, all changes are config-default flips, so they are
|
||||
non-breaking for anyone who already sets these fields explicitly and only change behavior for people
|
||||
relying on the current default.
|
||||
|
||||
### 1.1 Trusted Types default allowlist
|
||||
|
||||
**File:** `packages/core/src/headers.ts`, function `applyTrustedTypesDirectives`
|
||||
|
||||
**Problem:** `policyNames` defaults to `["*"]` in production, so any script — including an injected
|
||||
one — can register a Trusted Types policy. This defeats the XSS mitigation Trusted Types exists for.
|
||||
|
||||
**Change:**
|
||||
|
||||
```ts
|
||||
// Before
|
||||
const policyNames =
|
||||
typeof trustedTypes === "object" && trustedTypes.policyNames?.length
|
||||
? trustedTypes.policyNames
|
||||
: ["*"];
|
||||
|
||||
// After
|
||||
const policyNames =
|
||||
typeof trustedTypes === "object" && trustedTypes.policyNames?.length
|
||||
? trustedTypes.policyNames
|
||||
: ["wrnexus", "default"];
|
||||
```
|
||||
|
||||
Update the `TrustedTypesConfig.policyNames` doc comment to explain the new default and how to opt
|
||||
back into `["*"]` for apps with third-party extensions that need it.
|
||||
|
||||
**Tests:** update `packages/core/test/headers.test.ts` assertions that currently expect `*`.
|
||||
|
||||
**Docs:** update the "Production configuration baseline" block in `docs/SECURITY-PERFORMANCE-0.7.md`
|
||||
and add a migration note to `docs/UPGRADE-0.8.3.md`-style upgrade doc for 0.8.5.
|
||||
|
||||
### 1.2 HSTS `preload` default
|
||||
|
||||
**File:** `packages/core/src/headers.ts`, function `serializeHsts`
|
||||
|
||||
**Problem:** `preload: true` is on by default whenever `mode === "production"`. Preload-list
|
||||
submission is a long-lived commitment (removal takes months across browsers); defaulting it on for
|
||||
every production build is a footgun for teams not ready to guarantee HTTPS on every subdomain
|
||||
permanently.
|
||||
|
||||
**Change:**
|
||||
|
||||
```ts
|
||||
function serializeHsts(config: HstsConfig): string {
|
||||
const parts = [`max-age=${config.maxAge ?? 31536000}`];
|
||||
if (config.includeSubDomains !== false) parts.push("includeSubDomains");
|
||||
if (config.preload === true) parts.push("preload"); // was: !== false
|
||||
return parts.join("; ");
|
||||
}
|
||||
```
|
||||
|
||||
`includeSubDomains` can stay default-on (safe, reversible); only `preload` flips to opt-in.
|
||||
|
||||
**Tests:** update `packages/core/test/headers.test.ts`.
|
||||
|
||||
**Docs:** update `SECURITY-ASVS-5.md` row for `v5.0.0-3.4.1` evidence note and the production config
|
||||
baseline example (explicitly show `hsts: { preload: true }` as something apps opt into, with a
|
||||
one-line warning comment).
|
||||
|
||||
### 1.3 Live security-header verification (`--url` mode)
|
||||
|
||||
**File:** `packages/cli/src/security-command.ts`
|
||||
|
||||
**Problem:** `securityAudit()` already exists and is solid — it loads the local app config, builds a
|
||||
synthetic request/response, and runs `withSecurityHeaders` to check what headers _would_ be emitted.
|
||||
It never checks what a _deployed_ site is actually serving, so a misconfigured reverse proxy,
|
||||
missing env var, or config drift between local and production is invisible to `wrnexus security
|
||||
audit` today.
|
||||
|
||||
**Change:** add a second code path that takes a URL instead of an app root:
|
||||
|
||||
```ts
|
||||
export interface SecurityAuditOptions {
|
||||
appRoot?: string;
|
||||
/** Fetch a live deployment and audit its actual response headers instead of a local config. */
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export async function securityAudit(options: SecurityAuditOptions): Promise<SecurityAuditReport> {
|
||||
const headers = options.url
|
||||
? await fetchLiveHeaders(options.url)
|
||||
: await securityHeaders(resolve(options.appRoot ?? "."));
|
||||
// same checks[] logic runs against either header source
|
||||
...
|
||||
}
|
||||
|
||||
async function fetchLiveHeaders(url: string): Promise<Record<string, string>> {
|
||||
const res = await fetch(url, { method: "HEAD", redirect: "manual" });
|
||||
return Object.fromEntries(res.headers.entries());
|
||||
}
|
||||
```
|
||||
|
||||
CLI surface: `wrnexus security audit --url=https://workroot.in` — same `SecurityAuditCheck[]` table
|
||||
output as the local mode, so it's a drop-in mental model for anyone who's already used the local
|
||||
version.
|
||||
|
||||
**Tests:** `packages/cli/test/security-command.test.ts` — mock `fetch`, assert the same check IDs run
|
||||
against a header map built from a fake `Response`.
|
||||
|
||||
**Docs:** update `SECURITY-ASVS-5.md`'s intro line ("Run `bun run security:asvs` ... `wrnexus
|
||||
security audit`") to mention the `--url` mode explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — v0.9: Distributed rate limiting
|
||||
|
||||
### 2.1 `@wrnexus/ratelimit-redis`
|
||||
|
||||
**Problem:** `packages/core/src/ratelimit.ts` is honestly documented as process-local by default,
|
||||
with a clean `RateLimitStore` interface for swapping in a shared store — but no first-party
|
||||
implementation ships. Every team running more than one instance has to write their own Redis
|
||||
`INCR`/`PEXPIRE` bucket store before rate limiting actually works in production.
|
||||
|
||||
**New package layout** (mirrors `packages/captcha`'s `stores/redis.ts` pattern, which already
|
||||
exists for CAPTCHA — this is literally copying a pattern you've already built once):
|
||||
|
||||
```
|
||||
packages/ratelimit-redis/
|
||||
package.json
|
||||
src/
|
||||
index.ts # createRedisRateLimitStore()
|
||||
client.ts # thin ioredis/bun-redis wrapper, injectable client
|
||||
test/
|
||||
store.test.ts # against a real or mocked Redis
|
||||
README.md
|
||||
```
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
import { createRedisRateLimitStore } from "@wrnexus/ratelimit-redis";
|
||||
import { rateLimit } from "@wrnexus/core";
|
||||
|
||||
const store = createRedisRateLimitStore({ url: process.env.REDIS_URL! });
|
||||
app.use(rateLimit({ store, max: 100, windowMs: 60_000 }));
|
||||
```
|
||||
|
||||
Implementation: one atomic Lua script (`INCR` + conditional `PEXPIRE`) to avoid a race between the
|
||||
increment and the expiry set — same correctness bar as the in-memory store's atomicity within a
|
||||
single process.
|
||||
|
||||
**package.json:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "@wrnexus/ratelimit-redis",
|
||||
"version": "0.9.0",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"exports": { ".": "./src/index.ts" },
|
||||
"dependencies": { "@wrnexus/core": "workspace:*" }
|
||||
}
|
||||
```
|
||||
|
||||
**Tests:** `store.test.ts` covering window rollover, concurrent-hit correctness (fire N parallel
|
||||
`hit()` calls, assert exact count), and store failure fallback behavior (Redis down → fail open with
|
||||
a warning log, documented explicitly so nobody is surprised).
|
||||
|
||||
**Docs:** add a row to `SECURITY-SUPPORT-MATRIX.md` under a new "Rate limiting" area, and link it from
|
||||
the `RateLimitStore` doc comment in `packages/core/src/ratelimit.ts`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — v0.10: Product-critical DX packages
|
||||
|
||||
### 3.1 `@wrnexus/mail`
|
||||
|
||||
**Problem:** there's no first-party way to actually send email. The queue example
|
||||
(`app/queues/welcome-email.ts`) shows _scheduling_ an email job but nothing implements delivery.
|
||||
|
||||
**Package layout:**
|
||||
|
||||
```
|
||||
packages/mail/
|
||||
package.json
|
||||
src/
|
||||
index.ts
|
||||
send.ts # sendMail(), core envelope type
|
||||
providers/
|
||||
resend.ts
|
||||
ses.ts
|
||||
postmark.ts
|
||||
smtp.ts
|
||||
dev-inbox.ts # captures mail in dev instead of sending; wrnexus dev shows it in DevToolbar
|
||||
components/ # optional .wrn email-template partials, reusing the compiler
|
||||
test/
|
||||
send.test.ts
|
||||
dev-inbox.test.ts
|
||||
README.md
|
||||
SECURITY.md
|
||||
```
|
||||
|
||||
**API (mirrors the `SafeUrlPolicy`/provider-adapter shape from `@wrnexus/captcha`'s providers):**
|
||||
|
||||
```ts
|
||||
export interface MailProvider {
|
||||
send(message: MailMessage): Promise<MailResult>;
|
||||
}
|
||||
|
||||
export interface MailMessage {
|
||||
to: string | string[];
|
||||
from: string;
|
||||
subject: string;
|
||||
html?: string;
|
||||
text?: string;
|
||||
replyTo?: string;
|
||||
headers?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function createMailer(provider: MailProvider): { send(m: MailMessage): Promise<MailResult> };
|
||||
|
||||
// providers/resend.ts
|
||||
export function resendProvider(opts: { apiKey: string }): MailProvider;
|
||||
```
|
||||
|
||||
**Dev-mode behavior:** when `mode !== "production"`, `createMailer` wraps any provider with
|
||||
`devInboxProvider()`, which stores messages in memory and surfaces them in the DevToolbar (new panel,
|
||||
same pattern as the existing SQL/queue/realtime DevToolbar providers listed in
|
||||
`SECURITY-PERFORMANCE-0.7.md` §18) instead of actually sending — this is the single highest-value DX
|
||||
win in this package, since it removes the "did my email actually work" debugging loop entirely.
|
||||
|
||||
**Security notes for `SECURITY-SUPPORT-MATRIX.md`:** framework guarantees provider-secret handling
|
||||
stays server-side and never serializes into hydration payloads (reuse the existing serialization
|
||||
redaction from `@wrnexus/security/serialization.ts`); app/operator responsibility covers SPF/DKIM/DMARC
|
||||
DNS records and provider account reputation.
|
||||
|
||||
**Effort:** ~2–3 weeks for `resend` + `smtp` providers, dev inbox, and DevToolbar panel; `ses` and
|
||||
`postmark` can follow as a fast-follow since they share the same `MailProvider` interface.
|
||||
|
||||
### 3.2 `@wrnexus/flags`
|
||||
|
||||
**Problem:** no feature-flag primitive. Every team building past MVP eventually needs gradual
|
||||
rollout, and right now they'd bolt on a third-party SDK with no integration into WRNexusJS's
|
||||
`.wrn` reactivity or SSR model.
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
export interface FlagsStore {
|
||||
get(key: string, ctx: FlagContext): Promise<boolean | string | number>;
|
||||
}
|
||||
|
||||
export function createFlags(store: FlagsStore): {
|
||||
isEnabled(key: string, ctx: FlagContext): Promise<boolean>;
|
||||
variant(key: string, ctx: FlagContext): Promise<string | undefined>;
|
||||
};
|
||||
|
||||
// stores/memory.ts, stores/db.ts (reuses @wrnexus/db), stores/percentage.ts (deterministic hash rollout)
|
||||
```
|
||||
|
||||
`.wrn` integration: expose `load server { const enabled = await flags.isEnabled("new-dashboard",
|
||||
ctx) }` so flags flow into `props`/`state` the same way `load server` results already do — no new
|
||||
compiler syntax needed, just a helper package.
|
||||
|
||||
**Effort:** ~1–2 weeks; the DB-backed store reuses `@wrnexus/db` migration patterns already in the
|
||||
repo, so most of the work is the percentage-rollout hashing and the DevToolbar panel.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — v0.11: Search, paired with `@wrnexus/ai`
|
||||
|
||||
### 4.1 `@wrnexus/search`
|
||||
|
||||
**Problem:** no first-party search story, despite already having `@wrnexus/ai` for embeddings/LLM
|
||||
calls — search and RAG are the two things most SaaS apps need `@wrnexus/ai` _for_, so this is the
|
||||
natural pairing package.
|
||||
|
||||
**Package layout:**
|
||||
|
||||
```
|
||||
packages/search/
|
||||
src/
|
||||
index.ts
|
||||
adapters/
|
||||
postgres-fts.ts # tsvector + GIN index helpers, generated migration
|
||||
sqlite-fts5.ts
|
||||
pgvector.ts # embeddings via @wrnexus/ai, cosine-distance query helper
|
||||
typesense.ts # optional hosted adapter
|
||||
test/
|
||||
```
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
export function createSearchIndex(adapter: SearchAdapter, table: string, columns: string[]);
|
||||
const results = await search.query("customer onboarding", { limit: 10 });
|
||||
|
||||
// pairs directly with @wrnexus/ai
|
||||
import { embed } from "@wrnexus/ai";
|
||||
const vector = await embed(text);
|
||||
await search.upsertVector(id, vector);
|
||||
```
|
||||
|
||||
**Effort:** ~3–4 weeks — Postgres FTS and SQLite FTS5 adapters first (no new infra dependency,
|
||||
reuses `@wrnexus/db`'s existing driver abstraction), pgvector and Typesense as fast-follows.
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — v0.12: Enterprise & government readiness
|
||||
|
||||
This phase is prioritized specifically because `workroot.in` markets to "enterprises & governments" —
|
||||
these two items are the actual procurement blockers for that buyer, more than any generic framework
|
||||
feature would be.
|
||||
|
||||
### 5.1 SAML support in `@wrnexus/auth`
|
||||
|
||||
**Problem:** OAuth/OIDC is implemented (per `SECURITY-SUPPORT-MATRIX.md`), but large-enterprise and
|
||||
government IT departments frequently mandate SAML 2.0 specifically for SSO procurement, regardless of
|
||||
OIDC's technical merits.
|
||||
|
||||
**Location:** `packages/auth/src/saml/` — new subdirectory alongside the existing OAuth/OIDC code,
|
||||
same `AuthProvider` interface shape so it plugs into the existing account/session engine
|
||||
(`packages/auth/src/engine.ts`) without a parallel auth system.
|
||||
|
||||
**Scope:** SP-initiated SSO, signed assertion validation, configurable IdP metadata (Okta, Azure AD,
|
||||
Google Workspace, ADFS as the four IdPs to certify against first — that covers the large majority of
|
||||
enterprise/government IT estates).
|
||||
|
||||
**Security notes:** reuse the existing `AUTH_SECURITY_EVENT_TYPES` vocabulary for SAML-specific
|
||||
events (assertion replay attempt, signature validation failure, clock-skew rejection) so they flow
|
||||
into the same audit pipeline as every other auth event — no parallel logging system.
|
||||
|
||||
**Effort:** ~4–5 weeks; SAML assertion validation is fiddly (XML canonicalization, signature
|
||||
wrapping attacks) and deserves a dedicated security review pass before release, not just unit tests.
|
||||
|
||||
### 5.2 `@wrnexus/compliance`
|
||||
|
||||
**Problem:** `packages/authz/src/audit.ts` already has a well-built `AuthzAuditSink` interface with
|
||||
log-injection-safe formatting (`logSafe()`) and memory/console sinks — but no durable, exportable
|
||||
store. For SOC 2-style evidence or India's DPDP Act data-processing records, teams need retained,
|
||||
queryable, exportable audit trails, not console lines.
|
||||
|
||||
**Package layout:**
|
||||
|
||||
```
|
||||
packages/compliance/
|
||||
src/
|
||||
index.ts
|
||||
sinks/
|
||||
db.ts # durable AuthzAuditSink + auth security-event sink, reusing @wrnexus/db
|
||||
export.ts # CSV/JSON export with retention-window filtering
|
||||
retention.ts # configurable retention policy + scheduled purge
|
||||
test/
|
||||
```
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
import { dbAuditSink } from "@wrnexus/compliance";
|
||||
authz.configure({ auditSink: dbAuditSink({ retentionDays: 365 }) });
|
||||
|
||||
const report = await compliance.exportAuditTrail({ from, to, format: "csv" });
|
||||
```
|
||||
|
||||
This is the package I'd actually build _first_ internally for the WRNexus SaaS itself, since any
|
||||
enterprise/government customer of WRNexus will ask WorkRoot for exactly this evidence during their
|
||||
own procurement review — dogfooding it validates the design before it ships to other developers.
|
||||
|
||||
**Effort:** ~2 weeks on top of the existing audit-sink groundwork, since most of the hard part
|
||||
(safe event formatting, sink interface) is already done.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — v1.0: Monetization and ecosystem
|
||||
|
||||
### 6.1 `@wrnexus/billing`
|
||||
|
||||
**Problem:** no payments package, despite WRNexus itself being a billed SaaS product — this is the
|
||||
package where dogfooding value is highest.
|
||||
|
||||
**API:**
|
||||
|
||||
```ts
|
||||
export interface BillingProvider {
|
||||
createCheckoutSession(params): Promise<{ url: string }>;
|
||||
verifyWebhook(req: Request): Promise<BillingEvent>;
|
||||
getSubscription(customerId: string): Promise<Subscription>;
|
||||
}
|
||||
|
||||
// providers/stripe.ts, providers/razorpay.ts (India-relevant)
|
||||
```
|
||||
|
||||
Webhook verification reuses `@wrnexus/security`'s constant-time comparison helpers (same primitive
|
||||
already used in `packages/core/src/csrf.ts`'s `timingSafeEqual`) for signature checks. Usage metering
|
||||
hooks into `@wrnexus/observability`'s existing counters/gauges rather than a new metrics system.
|
||||
|
||||
**Effort:** ~5–6 weeks for Stripe + Razorpay, subscription lifecycle, and webhook handling with
|
||||
proper idempotency-key handling (a real source of billing bugs if skipped).
|
||||
|
||||
### 6.2 Public release strategy — open-core
|
||||
|
||||
**Problem:** `@wrnexus/*` is currently private, so nobody outside WorkRoot can `bun install` any of
|
||||
it. This is the actual ceiling on "developer power," not any single missing feature.
|
||||
|
||||
**Recommended split:**
|
||||
|
||||
| Tier | Packages | License |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
|
||||
| Open (public npm) | `core`, `ssr`, `compiler`, `syntax`, `router`, `store`, `reactive`, `security`, `ui`, `cli`, `dev-server`, `dev-toolbar` | MIT or Apache-2.0 |
|
||||
| Paid/enterprise | `billing`, `compliance`, SAML in `auth`, `authz` advanced policy engine | Commercial license, distributed via the private registry you already run |
|
||||
|
||||
**Rollout steps:**
|
||||
|
||||
1. Audit the `open` tier packages for any WorkRoot-specific secrets/config baked in (`scripts/`
|
||||
already has `generate-sbom.mjs` and `check-public-api.mjs` — extend `check-public-api.mjs` to also
|
||||
flag internal-only references before a package is promoted to the public tier).
|
||||
2. Publish under the `@wrnexus` npm org with the existing `PUBLISHING.md` process, starting with
|
||||
`core` + `cli` (the minimum to `bunx @wrnexus/cli create my-app` publicly).
|
||||
3. Public GitHub repo for the open tier only (mirrored from the monorepo via the existing
|
||||
`stage:packages` script's publish pipeline, not a manual copy).
|
||||
4. Keep `docs/ROADMAP.md`-style public roadmap visible so early external adopters see what's coming.
|
||||
|
||||
**Effort:** ~4–6 weeks of packaging/licensing/CI work, separate from any new feature work above — this
|
||||
can run in parallel with Phase 5.
|
||||
|
||||
### 6.3 Lean into `@wrnexus/ai` + `@wrnexus/mcp`
|
||||
|
||||
**Problem:** these packages already exist and are ahead of most frameworks, but aren't positioned as
|
||||
a headline feature anywhere in the marketing (`wrnexusjs.dev` homepage doesn't mention AI/MCP at all
|
||||
per the live screenshot taken earlier).
|
||||
|
||||
**Recommendation:** ship an official MCP server (`packages/mcp/src/index.ts` already has a `stdio.ts`
|
||||
transport — check whether it currently exposes framework introspection, e.g. route listing, `.wrn`
|
||||
component schema, or `wrnexus doctor` output as MCP tools) so agentic coding tools (Claude Code,
|
||||
Cursor, etc.) can scaffold and modify WRNexusJS apps with structured tool calls instead of guessing at
|
||||
the `.wrn` syntax from grepped examples. This is a low-cost, high-differentiation move given how much
|
||||
of the ecosystem is moving toward agent-built apps.
|
||||
|
||||
**Effort:** ~2–3 weeks to wrap existing CLI commands (`inspect`, `doctor`, `routes`, `generate`) as
|
||||
MCP tools, since the underlying logic already exists in `packages/cli/src`.
|
||||
|
||||
---
|
||||
|
||||
## Cross-cutting requirements for every phase
|
||||
|
||||
- Every new/changed package needs a `SECURITY-SUPPORT-MATRIX.md` row (framework guarantee vs.
|
||||
app/operator responsibility) before release, matching the existing table format.
|
||||
- Every new package needs an ASVS evidence row in `SECURITY-ASVS-5.md` if it touches auth, secrets,
|
||||
network requests, or user input — same two-column (implementation evidence / verification evidence)
|
||||
format already used.
|
||||
- `bun run validate:0.X` release gate (`scripts/validate-0.8.mjs`-style) should get a new
|
||||
`validate-0.9.mjs` etc. per phase, following the existing per-minor-version validation script
|
||||
pattern rather than one growing script.
|
||||
- `CHANGELOG.md` entries per release, same format as the 0.8.3/0.8.0 entries already there.
|
||||
- Each phase should ship an example page/route in `examples/basic-app` (mirroring
|
||||
`auth-showcase`, `captcha-showcase`, `i18n-showcase`) — e.g. `examples/mail-showcase`,
|
||||
`examples/billing-showcase` — so the roadmap's own "executable examples" discipline
|
||||
(`ROADMAP-COMPLETION-REPORT.md`) continues.
|
||||
|
||||
---
|
||||
|
||||
## Suggested sequencing rationale
|
||||
|
||||
Phase 1 ships first because it's the only phase with zero new surface area — pure default fixes that
|
||||
improve every existing deployment (including `workroot.in` and `wrnexusjs.dev` themselves) without
|
||||
anyone changing their code. Phases 2–4 build developer-facing power in the order teams actually hit
|
||||
the wall (rate limiting under real traffic → needing email → needing flags → needing search once an
|
||||
app has enough data to search). Phase 5 is prioritized ahead of Phase 6 despite being harder, because
|
||||
it directly unblocks revenue-relevant deals in WorkRoot's actual pipeline (enterprise/government
|
||||
procurement). Phase 6's public release is last on purpose — it should launch once there's a stronger
|
||||
package lineup behind it, so the first public impression of `@wrnexus/*` is "batteries-included," not
|
||||
"promising but thin."
|
||||
@@ -2858,7 +2858,8 @@
|
||||
]
|
||||
},
|
||||
"@wrnexus/ui": {
|
||||
".": [
|
||||
".": [],
|
||||
"./registry": [
|
||||
"UiComponentMetadata",
|
||||
"UiComponentReference",
|
||||
"auditUiComponents",
|
||||
|
||||
@@ -27,18 +27,18 @@
|
||||
"packages/ui/components/Combobox.wrn": "62a7d861b0e536ceb088cea3552c59acbafaea5184101e3651dc8f4976b99dbc",
|
||||
"packages/ui/components/Confetti.wrn": "22d687beefd5047055f65e806b4e39f81e18887532ec18d439ac364a044b230a",
|
||||
"packages/ui/components/Container.wrn": "be8fce140043eced8b78c8dd14ff85ace4ed2d91671251641ef83e0fe4da8f2a",
|
||||
"packages/ui/components/ContextMenu.wrn": "2b5153f3acec3b11e5f20d7ea219f4e7b92569340fac09279e6a51bff0e7b235",
|
||||
"packages/ui/components/ContextMenu.wrn": "2e011e9cb1c09a3a2344ed3fa29dcc74331cd39f838535d8aee4b249dceef0ce",
|
||||
"packages/ui/components/CopyMarkup.wrn": "8d57a7d72e02c126f855a181fc36e1b969edb90f683dce6b6ffe546d49d2b29b",
|
||||
"packages/ui/components/CustomScrollbar.wrn": "4c1f7758b9cd47e20ecf922403e12b5b15b8127a2744383c6bd280ee20e5fdd1",
|
||||
"packages/ui/components/DataMap.wrn": "65552d73ecd1a148427dbb10d611f9352ca36bd490f595ab1e1a47e173445ba1",
|
||||
"packages/ui/components/DataTable.wrn": "a147aef9840ac1ed9e97921b8ff81b4f8a5afe66dd6ccb6d26947b8f7ffc760f",
|
||||
"packages/ui/components/DataTable.wrn": "5a76e50e8559c724aaf01ec954958ac640c99758d44f1eef8e6eb1cb6dc4f862",
|
||||
"packages/ui/components/DatePicker.wrn": "18b07e59c27c2720bf960e9c04ad70d9e422ddcf0817fede74e71899811bb387",
|
||||
"packages/ui/components/DeviceFrame.wrn": "a881cf2cc1f859b43cc3c7a1e1ca0fb40cea7a9e530ab5bdcb985aa9592d11c0",
|
||||
"packages/ui/components/Divider.wrn": "e377f8005249cf4f6c51dd4d04ff9c6a70a3f4e3a79d2c02a8ab601754c8553a",
|
||||
"packages/ui/components/DragAndDrop.wrn": "9a403ce9ed20d36211d8916911871875c9c7d0952a7de1a21a433eaa0b3c3324",
|
||||
"packages/ui/components/Drawer.wrn": "7ecfc54494402e8758eb3f648418df70b6cde598f38836a620a1fa179d1bb1bd",
|
||||
"packages/ui/components/Dropdown.wrn": "703c354dfb304ad640a600398317b0a5eefeea326df8a1fc51c87e0ed3c26ebc",
|
||||
"packages/ui/components/FeatureCard.wrn": "cf122df37d5de72a9556fea501b13b8bfad49ee4a5e88daec2a32d204ee9e784",
|
||||
"packages/ui/components/Drawer.wrn": "04a77a69edfad6793b5ab60981a719f11061984c087d0d61663566e322649fef",
|
||||
"packages/ui/components/Dropdown.wrn": "c61b46ae6e54f6aacac9f4ed422f6c659deea9a637fd944645acd37bf270b01d",
|
||||
"packages/ui/components/FeatureCard.wrn": "a6afd6d4080917106b2c504ea3836b92ac3c18e85f1aafd74ffe49592782b5ec",
|
||||
"packages/ui/components/FeatureGrid.wrn": "9e3c0440d08c6982861d3e732eeb20f1023413a58243844dfb9d7033bac13d2b",
|
||||
"packages/ui/components/FeatureIconCard.wrn": "afe56213543dd78932b060fa19c4e645e558b211b380c42b6d4b0d18e9d543df",
|
||||
"packages/ui/components/FileInput.wrn": "8b63811deb90a03763620bedf0d5d3c7d05a20b8ae34292eaface9b756b32ecd",
|
||||
@@ -64,13 +64,13 @@
|
||||
"packages/ui/components/MegaMenu.wrn": "4a084eaf6aae77bb9023d2f3589bc6b80119b9be63982a90a80f9d280cc9c0a5",
|
||||
"packages/ui/components/MetricCard.wrn": "6451182739298691908f68258c0250cce2a78b0dc27c97115579ece30d7d9f92",
|
||||
"packages/ui/components/MetricGrid.wrn": "6018a98c10628ed240ed236ed916c0ff60994d192c3aadafd10bc876be0f9364",
|
||||
"packages/ui/components/Modal.wrn": "1821428492e510403dd029c4766e71272899299f65141ae4fdaf09c4a26719e5",
|
||||
"packages/ui/components/Modal.wrn": "59d4ae9d6dd2700692d9edecfe53ee868e9f864363a4885961d1813cb2bee51f",
|
||||
"packages/ui/components/Nav.wrn": "78f215c94caf68e0968449a23e23bd3409a689e0c52a6c1770aa8373c767d080",
|
||||
"packages/ui/components/Navbar.wrn": "e68f9d3643e500e43124e9c7a6d4c7f6722657377ea7313f3e3cf5093a81b360",
|
||||
"packages/ui/components/PageHeader.wrn": "adb3bed81ce040044405e35a52d0304f49d5a0d162f923fbd54a4242a25c7362",
|
||||
"packages/ui/components/PageHeader.wrn": "0761235a4924eec09877be40b292d27b70db22d210be7d8e989493165c20df86",
|
||||
"packages/ui/components/Pagination.wrn": "9169e724f89992dacd10e9492a95438c8016affb39c0fd17a4f6fe26bd21d8b4",
|
||||
"packages/ui/components/PinInput.wrn": "5196f584de8d548a5dfa03688c3c95da3c948cead2662b05e927386ccea74299",
|
||||
"packages/ui/components/Popover.wrn": "fd9982f60e37e500586f788470f41a8510d6b96d3a85c80d2859fa01cac6731e",
|
||||
"packages/ui/components/Popover.wrn": "167f6c476cf3114ac5062ecf7739bddd9b5a81b1d209396a3675067dad1577b2",
|
||||
"packages/ui/components/PortalDashboard.wrn": "037d4300b59c7d60543abc7d4aba5c738efc143e9b61abc48aad0ddfcfe6845b",
|
||||
"packages/ui/components/PreferenceSwitcher.wrn": "2cc186d4dcb6580b4b152e3a265d9ed5ad210d3fdc76330b9092db21894465f0",
|
||||
"packages/ui/components/PublicPageShell.wrn": "507baad0e83dc05c24db42b8af8bd45b18f4e32e418843d77ec906428c687199",
|
||||
@@ -96,9 +96,10 @@
|
||||
"packages/ui/components/Timeline.wrn": "5708c656eefd12f31c93490075ee482bf527059028844bd58cdcddc88461e2b5",
|
||||
"packages/ui/components/Toast.wrn": "f37c584d1c1a66401deb53d915c8ee0c70aaf7baa1d3c297fa74c5bdb914dd1d",
|
||||
"packages/ui/components/ToastNotifications.wrn": "33ff76b2a0a129ff896baea8979b4be97f7ec4471a92a2da970403a5c46e8b03",
|
||||
"packages/ui/components/Toaster.wrn": "7481ecddde9ed5bf1f448d45a78da7ee0009f84963814bf681735db4e5ab75f9",
|
||||
"packages/ui/components/ToggleCount.wrn": "70a75b2bdcc89103f8ca300ee6d21cd61baf8c6a4aeade9b68d1dc529f77064b",
|
||||
"packages/ui/components/TogglePassword.wrn": "405a85cbfa3d0ff0185b52d2805fba88497a5f25501f01b2438ed3a28591a38d",
|
||||
"packages/ui/components/Tooltip.wrn": "fe663c153e5239f37a298273662b16a8a77928be9376a823cd6f4e62fbaf2ee5",
|
||||
"packages/ui/components/Tooltip.wrn": "e6c8c14a75062d04a2e64245df1a40e67785240573c73bae4b0b32e76ef822bc",
|
||||
"packages/ui/components/TreeView.wrn": "1791a10135595b17b5c746af9e0476e8520d516ba46d64c79dc4efe0af2b500d",
|
||||
"packages/ui/components/Typography.wrn": "a667e800e11d23da74b00047bd9c560a65a5920e5394f58f66a6e0b32572ce8e",
|
||||
"packages/ui/components/WysiwygEditor.wrn": "636e60b9f9be7a5807cca7ad20e0b0f370ee1ec6c1ecba5d0a577726cc86bc98",
|
||||
@@ -109,7 +110,6 @@
|
||||
"packages/ui/components/progress.wrn": "ba6f4dfcc00f04f34e9533be675bb4a6200c3cdcd7d03fc597377e48520fdec7",
|
||||
"packages/ui/components/skeleton.wrn": "ceec8af147b8be08155500e378393ac7c72d819da61b62191c076c80e943d5a9",
|
||||
"packages/ui/components/spinner.wrn": "abc4ee3ede2e019289257e9009eee012c880a85839cc220fac6dbe1b780caf52",
|
||||
"packages/ui/components/table.wrn": "96126772e82fbd401787600aba7a095ef097282d9bdb4df22f3e06016c677210",
|
||||
"packages/ui/ui.css": "a72e839e6cccbe01483507f9d9ba913962aca2b9f866bb6e136f023d6ea2013d"
|
||||
"packages/ui/ui.css": "eee7da4d1088703a61db8e78c43eb6ff7f1b6a02fd8082d8d722beb65bf78382"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user