Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/build-scope-source-condition.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[fix] Scope the `source` resolve condition to @astryxdesign packages in withAstryx

`withAstryx` set webpack's `conditionNames` to `['source', …]` globally, which resolved *any* dependency shipping a `source` export to its raw TypeScript — not just Astryx packages. Third-party deps that ship a `source` export (e.g. `lexical`, pulled in by the new RichTextEditor lab component) were then fed untranspiled `.ts` through Next's babel and failed on syntax like `declare` class fields.
`withAstryx` set webpack's `conditionNames` to `['source', …]` globally, which resolved _any_ dependency shipping a `source` export to its raw TypeScript — not just Astryx packages. Third-party deps that ship a `source` export (e.g. `lexical`, pulled in by the new RichTextEditor lab component) were then fed untranspiled `.ts` through Next's babel and failed on syntax like `declare` class fields.

The `source` condition is now applied via a scoped **allowlist** `module.rules` entry that only matches `node_modules/@astryxdesign/*` (with `conditionNames: ['source', '...']`, so it augments rather than replaces Next's defaults). The global `config.resolve.conditionNames` is left as Next's default, so all other packages — including any future third-party dep that happens to ship a `source` export — resolve to their built output. React JSX resolution is preserved via the inherited `'...'` defaults + `react-server` condition. Astryx source builds are unaffected.

Expand Down
20 changes: 20 additions & 0 deletions .changeset/table-row-status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@astryxdesign/core': patch
---

[feat] Add `useTableRowStatus`, a plugin that prepends a narrow column
signaling per-row status.

- Compact per-row status signal (error, warning, unread, etc.) without a
dedicated status column: a colored status dot by default, or an icon when
provided.
- `getStatus(item)` maps a row to `{color, icon?, label?}`, or `null` for no
indicator. `color` accepts a semantic status name
(`success`/`error`/`warning`/`accent`/`red`/`green`/etc.) mapped to a theme
token, or a raw CSS color as an escape hatch.
- `icon` renders the status as a shape signifier instead of the dot, which is
more accessible than color alone when multiple statuses coexist. `label`
supplies the accessible name.
- Memoize `getStatus` with `useCallback` for a stable plugin identity.

@humbertovirtudes
114 changes: 57 additions & 57 deletions .github/instructions/packages.instructions.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions apps/sandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,12 @@ Edits trigger incremental dist rebuilds via Babel CLI (a few seconds), and CSS l

| File | Purpose |
| -------------------------- | ------------------------------------------------------------------------ |
| `package.json` | Dependencies; uses PostCSS path for StyleX |
| `package.json` | Dependencies; uses PostCSS path for StyleX |
| `babel.config.js` | StyleX babel plugin config (as plugin, not preset) |
| `postcss.config.js` | StyleX PostCSS plugin: extracts CSS from `@stylex;` directive |
| `postcss.config.js` | StyleX PostCSS plugin: extracts CSS from `@stylex;` directive |
| `next.config.mjs` | Static export, basePath for GitHub Pages, webpack alias for theme tokens |
| `tsconfig.json` | TypeScript config with workspace path aliases |
| `src/app/globals.css` | `@stylex;` injection point: PostCSS replaces this with extracted CSS |
| `src/app/globals.css` | `@stylex;` injection point: PostCSS replaces this with extracted CSS |
| `src/app/providers.tsx` | Client-side theme provider wrapper |
| `src/app/layout.tsx` | Root layout with sidebar navigation |
| `src/app/Sidebar.tsx` | Sidebar navigation component |
Expand Down
28 changes: 27 additions & 1 deletion apps/sandbox/src/app/(sandbox)/pages/table-lab/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
useTableRowExpansionState,
useTableGroupedRows,
useTableRowIndex,
useTableRowStatus,
} from '@astryxdesign/core/Table';
import type {TablePlugin, TableSortState} from '@astryxdesign/core/Table';

Expand All @@ -72,7 +73,8 @@ type PluginId =
| 'stickyColumns'
| 'rowExpansion'
| 'groupedRows'
| 'rowIndex';
| 'rowIndex'
| 'rowStatus';

interface PluginMeta {
id: PluginId;
Expand Down Expand Up @@ -117,6 +119,11 @@ const PLUGIN_REGISTRY: PluginMeta[] = [
label: 'Row Index',
description: 'Prepend a monospaced row-number column',
},
{
id: 'rowStatus',
label: 'Row Status',
description: 'Status dot / icon (by member status)',
},
];

// =============================================================================
Expand Down Expand Up @@ -244,6 +251,21 @@ function useLabPlugins({
getRowKey: item => item.id,
});

// --- row status ---
const rowStatusPlugin = useTableRowStatus<LabRow>({
getStatus: item => {
// Semantic color names map to theme tokens; an icon adds a shape
// differentiator so status isn't conveyed by color alone.
if (item.status === 'Active') {
return {color: 'success', icon: 'success', label: 'Active'};
}
if (item.status === 'Away') {
return {color: 'warning', icon: 'warning', label: 'Away'};
}
Comment thread
humbertovirtudes marked this conversation as resolved.
return null; // Offline: no indicator
},
});

// Assemble enabled plugins in a stable order.
const plugins: Record<string, TablePlugin<LabRow>> = {};
if (enabled.groupedRows) {
Expand All @@ -252,6 +274,9 @@ function useLabPlugins({
if (enabled.rowIndex) {
plugins.rowIndex = rowIndexPlugin;
}
if (enabled.rowStatus) {
plugins.rowStatus = rowStatusPlugin;
}
if (enabled.sortable) {
plugins.sort = sortPlugin;
}
Expand Down Expand Up @@ -431,6 +456,7 @@ export default function TableLabPage() {
rowExpansion: false,
groupedRows: false,
rowIndex: false,
rowStatus: false,
});

const baseData = useMemo(() => generateRows(rowCount), [rowCount]);
Expand Down
5 changes: 1 addition & 4 deletions apps/storybook/stories/RichTextEditor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import {useState} from 'react';
import type {Meta, StoryObj} from '@storybook/react';
import {
RichTextEditor,
RichTextView,
} from '@astryxdesign/lab';
import {RichTextEditor, RichTextView} from '@astryxdesign/lab';
import type {EditorState} from 'lexical';

const meta: Meta<typeof RichTextEditor> = {
Expand Down
101 changes: 101 additions & 0 deletions apps/storybook/stories/TableRowStatus.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) Meta Platforms, Inc. and affiliates.

import type {Meta, StoryObj} from '@storybook/react';
import {
Table,
useTableRowStatus,
proportional,
pixel,
} from '@astryxdesign/core/Table';
import type {TableColumn, TableRowStatus} from '@astryxdesign/core/Table';

// =============================================================================
// Sample Data
// =============================================================================

interface Job extends Record<string, unknown> {
id: string;
name: string;
owner: string;
state: 'failed' | 'running' | 'queued' | 'succeeded';
}

const jobs: Job[] = [
{id: 'j1', name: 'build-core', owner: 'Ava', state: 'failed'},
{id: 'j2', name: 'lint', owner: 'Liam', state: 'running'},
{id: 'j3', name: 'unit-tests', owner: 'Zoe', state: 'succeeded'},
{id: 'j4', name: 'docsite-deploy', owner: 'Max', state: 'queued'},
{id: 'j5', name: 'smoke-test', owner: 'Mia', state: 'succeeded'},
];

const columns: TableColumn<Job>[] = [
{key: 'name', header: 'Job', width: proportional(2)},
{key: 'owner', header: 'Owner', width: pixel(120)},
{key: 'state', header: 'State', width: pixel(120)},
];

function jobStatus(job: Job): TableRowStatus | null {
switch (job.state) {
case 'failed':
return {color: 'error', icon: 'error', label: 'Failed'};
case 'running':
return {color: 'warning', icon: 'warning', label: 'Running'};
case 'queued':
return {color: 'gray', label: 'Queued'};
default:
return null; // succeeded: no indicator
}
}

const meta: Meta = {
title: 'Core/TableRowStatus',
tags: ['autodocs'],
};

export default meta;
type Story = StoryObj;

/**
* A small colored dot in a leading gutter column signals per-row status.
* Rows whose `getStatus` returns `null` (here: succeeded jobs) show no dot.
* Hover a dot to see its accessible label.
*/
export const Default: Story = {
render: () => {
const rowStatus = useTableRowStatus<Job>({getStatus: jobStatus});
return (
<Table
data={jobs}
columns={columns}
idKey="id"
hasHover
plugins={{rowStatus}}
/>
);
},
};

/**
* Any CSS color works: here raw hex values instead of theme tokens.
*/
export const RawColors: Story = {
render: () => {
const rowStatus = useTableRowStatus<Job>({
getStatus: job =>
job.state === 'failed'
? {color: '#dc2626', label: 'Failed'}
: job.state === 'running'
? {color: '#f59e0b', label: 'Running'}
: null,
});
return (
<Table
data={jobs}
columns={columns}
idKey="id"
hasHover
plugins={{rowStatus}}
/>
);
},
};
17 changes: 11 additions & 6 deletions internal/vibe-tests/prompt-purity-test/ITERATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ re-test a wording:

- Path: `"$TMPDIR/astryx-purity/_loop/iterations-log.json"` (outside the repo, like all
runtime artifacts). Create it on the first tick if absent: `{ "tick": 0, "champion":
"B-selfcheck", "history": [] }`.
"B-selfcheck", "history": [] }`.
- Each `history[]` entry: `{ tick, expId, ranConditions, variantHashes, rates: { <cond>:
{ veeredUncaught, caughtGivenVeered, finalPurityMean, n } }, note }`.
{ veeredUncaught, caughtGivenVeered, finalPurityMean, n } }, note }`.
- "champion" = the best variant found so far (lowest `veeredUncaught`, tie-broken by
higher `finalPurityMean`). Seed champion = `B-selfcheck`.

Expand All @@ -41,12 +41,14 @@ re-test a wording:
## One tick

### 1. Read where we are

- Read `iterations-log.json`. Identify the current `champion`.
- If `history` is empty, this is the seed tick: run `A-control`, `B-selfcheck`,
`C-strong` together (exploration settings below) to establish the baseline and pick
the first champion. Skip step 2 on the seed tick.

### 2. Author ONE new challenger (skip on the seed tick)

- Based on the last run's per-run detail in `purity-summary.json` (which markers survived
in `veeredUncaught` runs, whether `ranDocRetrieval` was low, whether catches happened
late), write ONE new variant wording that targets the observed failure mode. Ideas to
Expand All @@ -59,10 +61,11 @@ re-test a wording:
- Write it to a NEW file `variants/g<NN>-<slug>.md` (never overwrite A/B/C or a prior
generation — history must stay reproducible). Add a matching entry to
`conditions.json` `conditions[]`: `{ "id": "g<NN>-<slug>", "role": "candidate",
"variant": "g<NN>-<slug>" }`.
"variant": "g<NN>-<slug>" }`.
- Do NOT test a wording whose text hash already appears in `history`.

### 3. Run (exploration = cheap, fast)

- Compare exactly THREE conditions so runs stay focused: control, champion, challenger.
- Explore settings: `--prompts dd-1,tc-6 --reps 3`.
- Start it in the BACKGROUND and arm the completion wake (dynamic `/loop`). Generate an
Expand All @@ -76,11 +79,12 @@ EXP=$(node -e "console.log(require('crypto').randomBytes(4).toString('hex'))")
echo "AGENT_LOOP_WAKE_purity {\"expId\":\"$EXP\",\"phase\":\"explore\"}" ) 2>&1
```

Launch via the Shell tool with `block_until_ms: 0` and `notify_on_output` on
`^AGENT_LOOP_WAKE_purity`. Also arm ONE long fallback heartbeat (e.g. `sleep 1800`)
per the loop skill. Then END THE TURN — do not block.
Launch via the Shell tool with `block_until_ms: 0` and `notify_on_output` on
`^AGENT_LOOP_WAKE_purity`. Also arm ONE long fallback heartbeat (e.g. `sleep 1800`)
per the loop skill. Then END THE TURN — do not block.

### 4. On wake — evaluate

- Read `"$TMPDIR/astryx-purity/$EXP/purity-summary.json"`.
- Append a `history` entry (rates for each condition + variant hashes).
- Compare challenger vs champion: challenger becomes the new champion if its
Expand All @@ -89,6 +93,7 @@ EXP=$(node -e "console.log(require('crypto').randomBytes(4).toString('hex'))")
`veeredUncaught`, `caughtGivenVeered`, purity — and whether the champion changed.

### 5. Decide: confirm, continue, or stop

- **Promote to confirmation** when the champion beats `A-control` in exploration
(`veeredUncaught` lower AND purity higher). Run a CONFIRMATION:
`--conditions A-control,<champion> --prompts dd-1,dd-5,wd-1,wd-4,tc-6,ps-1 --reps 7`
Expand Down
6 changes: 3 additions & 3 deletions internal/vibe-tests/prompt-purity-test/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CSS / Tailwind / inline styles / hardcoded values instead of Astryx components +
**self-verifies** its output and **catches itself** before finishing.

> Does adding a post-generation self-check to the `astryx init` prompt make agents
> reliably (a) *not* leave raw CSS/Tailwind in the final file, and (b) when they do
> reliably (a) _not_ leave raw CSS/Tailwind in the final file, and (b) when they do
> start to veer, **catch and correct it** — measured from the actual run trajectory?

## 2. What varies (independent variable)
Expand Down Expand Up @@ -57,8 +57,8 @@ to verify — from the logging shim), and a transcript scan for "caught-in-reaso
`stylex` styling system (the Astryx path), a logging `astryx` shim, then real
`astryx init` + the variant splice. Writes `tasks/<id>.json` + `purity-config-<expId>.json`.
- `run-purity.ts` — runner. Shells out to the `cursor-agent` CLI headless (`--print
--output-format stream-json --model claude-opus-4-8-max-fast --force --trust
--workspace <sandbox project>`), snapshots the `.tsx` on every change, persists the
--output-format stream-json --model claude-opus-4-8-max-fast --force --trust
--workspace <sandbox project>`), snapshots the `.tsx` on every change, persists the
streamed events to `transcript.jsonl`, records runtime facts to `run.json`. Bounded
concurrency (`--concurrency`, default 3) + per-run timeout so it never starves
interactive Cursor usage.
Expand Down
Loading
Loading