feat(ui): add DataTable and Toaster, drop the legacy Table, fix overlay dialogs
Quality / quality (ubuntu-latest) (push) Failing after 13m40s
Quality / quality (windows-latest) (push) Canceled after 0s

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:
2026-08-07 14:59:58 +05:30
co-authored by Claude Opus 5
parent 296728d51d
commit 949cf78636
151 changed files with 14350 additions and 9189 deletions
+500
View File
@@ -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 | 12 weeks |
| 2 | 0.9 | Distributed rate limiting + live security audit | `@wrnexus/ratelimit-redis` | 23 weeks |
| 3 | 0.10 | Product-critical DX packages | `@wrnexus/mail`, `@wrnexus/flags` | 46 weeks |
| 4 | 0.11 | Search + AI pairing | `@wrnexus/search` | 34 weeks |
| 5 | 0.12 | Enterprise/government readiness | SAML in `@wrnexus/auth`, `@wrnexus/compliance` | 57 weeks |
| 6 | 1.0 | Monetization + ecosystem | `@wrnexus/billing`, public release strategy | 610 weeks |
Total: roughly 68 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:** ~23 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:** ~12 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:** ~34 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:** ~45 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:** ~56 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:** ~46 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:** ~23 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 24 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."