Skip to content
Open
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
9 changes: 9 additions & 0 deletions .changeset/connection-timeouts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@noormdev/sdk": minor
---

## Connection

### Added

* `feat(db):` Connections now carry a connect timeout, so an unreachable host fails instead of hanging forever. Defaults to 15s; set `connection.connectTimeoutMs` per config to raise it for a database that resumes from an auto-paused state.
24 changes: 24 additions & 0 deletions .changeset/tui-ink7-and-row-inspection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"@noormdev/cli": minor
---

## TUI

### Added

* `feat(tui):` The interactive UI now draws in the alternate screen at full terminal height, and restores the terminal on exit instead of leaving itself in scrollback.
* `feat(tui):` Forms are a two-column layout with browse and edit modes. Arrow keys move between fields on every field type including selects, `Enter` opens a field and commits it, `Esc` reverts it, and submit is a `[ Save ]` row you navigate to.
* `feat(tui):` Lists size themselves to the terminal instead of a fixed row count, and keep their cursor when you leave a screen and come back.
* `feat(explore):` `r` on a table shows its first and last rows, ordered by primary key. `Enter` opens a row as YAML or JSON, `f` switches format, and the arrow keys walk between rows.
* `feat(explore):` The detail screen scrolls, and `v` shows a value the column grid had to truncate.
* `feat(sql):` Wide result grids drop whole columns behind a `… N more columns` marker rather than squeezing every column past legibility. `Enter` opens a row in full.
* `feat(tui):` Mouse support: click to move the cursor, double-click to activate, wheel to scroll. Set `ui.mouse: false` in `.noorm/settings.yml` to restore click-drag text selection.
* `feat(db):` `Esc` cancels a connection test or query that is hanging. On PostgreSQL and MySQL the server is asked to stop the query; on SQL Server and SQLite the client stops waiting and says so.

### Fixed

* `fix(sql):` Backspace in the results filter did nothing, because the key reports as `delete` on the previous Ink release.
* `fix(tui):` Screens sized from the terminal never recomputed on resize.
* `fix(explore):` Column, index and parameter lists re-flowed per row, so the type column landed on a different offset on nearly every row.
* `fix(explore):` "Total Objects" counted categories the screen does not list, so it exceeded the rows a reader could see.
* `fix(tui):` The help screen and log viewer drew past the bottom of the window, putting their first lines out of reach.
42 changes: 15 additions & 27 deletions .claude/rules/documentation.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,33 @@
---
paths: docs/**/*.md
paths:
- "docs/**/*.md"
---

# Documentation Rules
# Documentation rules


## Three-Pillar Structure
## Voice is assigned per file, not globally

Build each section with Memory, Reasoning, and Example woven together. The pillars are invisible scaffolding.
The `## Documentation surfaces` table in the root `CLAUDE.md` assigns every doc path a voice: `atomic-writing` or `terse-technical`. That table is the authority, and `/documentation` reads it to route authoring. Look the file up there before writing, and follow the `atomic-writing` skill when it says so.

| Pillar | Purpose | Expression |
|--------|---------|------------|
| Memory | The what | Concepts, definitions, data structures |
| Reasoning | The why | Motivation, trade-offs, design decisions |
| Example | The how | Code samples, workflows, practical demonstrations |
Do not impose one house tone across `docs/`. A CLI reference and a getting-started guide are labeled differently on purpose.


## Style

Start with the problem the reader wants to solve. Explain through analogy before technical detail.

Use short sentences mixed with longer explanations. Show "why" before "how". Code follows explanation.

Create visual hierarchy through headers, code blocks, and tables.
## Claims

Every claim has to be one you can point at.

## Tone
Do not invent a position to argue against. No "some people say", no "you may have been told", no opponent who does not hold the view. State the finding and let it stand without a foil.

Conversational but precise. Guide the reader from familiar concepts to new ones.
`docs/guide/changes/overview.md:15` currently breaks this ("Some argue that you *are* moving data..."). Fix it when you next touch that file, and do not copy the shape.

Measurements carry their conditions: the tool, the scale, the run. Never combine numbers from different runs or builds into one comparison. If the conditions differ, re-measure or say which figure came from where.

## Claims
Mark inference as inference. An extrapolation from a mechanism reads as a measured result unless the text says otherwise.

Every claim has to be one you can point at.

Do not invent a position to argue against. No "some people say", no "you may have been told", no
opponent who does not hold the view. State the finding and let it stand without a foil.
## Structure

Measurements carry their conditions — the tool, the scale, the run. Never combine numbers from
different runs or builds into one comparison. If the conditions differ, re-measure or say which
figure came from where.
Lead with the problem the reader wants to solve, then the mechanism. Code follows the explanation that motivates it.

Mark inference as inference. An extrapolation from a mechanism reads as a measured result unless
the text says otherwise.
Prefer a table, tree, or diagram wherever the content has a shape. Reserve prose for reasoning and motivation, which is the one thing a table cannot carry.
85 changes: 61 additions & 24 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -1,58 +1,95 @@
---
paths: tests/**/*.{ts,tsx}
paths:
- "tests/**/*.{ts,tsx}"
---

# Testing Rules
# Testing rules


## Naming
Runner is `bun:test`. `bun run test` is `bun test --serial`, and `bunfig.toml` pins `concurrency = 1`, `timeout = 30000`, and `preload = ["./tests/preload.ts"]`. All 315 test files import from `'bun:test'`; nothing here uses vitest or jest.

Use `describe('module: feature', () => {})` format. Group by module, then by feature.

```ts
describe('runner: executeFile', () => {
## Before running anything

it('should skip unchanged files', async () => {
Integration tests need live databases on non-default ports (postgres `15432`, mysql `13306`, mssql `11433`):

// ...
```bash
docker compose -f docker-compose.test.yml up -d
```

});
`skipIfNoContainer(dialect)` (`tests/utils/db.ts:892`) **throws, it does not skip**, despite the name. 40 files call it in `beforeAll`. Without the containers up you get a wall of failures that read like real regressions. Check the containers before you believe a red suite.

it('should emit error event on failure', async () => {

// ...
## Database safety

});
The suite runs `TRUNCATE`, `DROP`, and teardown. `assertTestDatabase` (`tests/utils/db.ts:122`) refuses to connect unless the resolved database name is `:memory:` or contains `test` as a `_`/`-` delimited word, and `createTestConnection` calls it on every connection. It exists so a stray `.env` or leaked CI secret cannot aim a destructive suite at a real database.

});
```
Never bypass it by building a Kysely instance by hand. Route test connections through `createTestConnection`.


## Coverage
## Naming

Test all paths: success, error, edge cases. Verify observer events are emitted with correct data.
`describe('module: feature', ...)` — 414 of 420 top-level describes follow this. Group by module, then by feature. Prefix `it` descriptions with "should".

```ts
it('should emit error event on failure', async () => {
describe('runner: executeFile', () => {

const events: any[] = [];
observer.on('file:after', (data) => events.push(data));
it('should skip unchanged files', async () => {

const [_, err] = await attempt(() => executeFile(badFile, configName));
// ...

expect(err).toBeInstanceOf(Error);
expect(events[0].status).toBe('failed');
});

});
```


## Error Handling
## Errors

Use `attempt` for operations that may fail. Assert both the error and any side effects.
Use `attempt`/`attemptSync` and assert on the tuple. Assert the error *and* the side effects that should or should not have happened.

```ts
const [result, err] = await attempt(() => executeFile(badPath));

expect(err).toBeInstanceOf(InvalidFileError);
expect(result).toBeUndefined();
```

The no-`try/catch` rule in `typescript.md` covers `tests/**` too, and 17 blocks currently violate it. Do not add more.


## Observer events

Modules that emit events should have their emissions asserted, not just their return values. This applies to the ~20 files testing event-emitting modules (`core/change/executor`, `core/transfer/*`, `core/lifecycle/*`, and similar), not to the suite at large.

```ts
const events: ChangeEvent[] = [];
observer.on('file:after', (data) => events.push(data));

const [, err] = await attempt(() => executeFile(badFile, configName));

expect(err).toBeInstanceOf(Error);
expect(events[0].status).toBe('failed');
```


## Module mocking

Bun's `mock.module` registry is process-global and never restores, so a mocking file poisons every file loaded after it. CI works around this by splitting the suite into five separate processes. The mechanics and the reasoning are in the root `CLAUDE.md`; read it before adding `mock.module` to a new file.


## Helpers

`tests/utils/db.ts` is the shared database harness:

| Export | Purpose |
|--------|---------|
| `createTestConnection` | guarded connection; use instead of building Kysely directly |
| `assertTestDatabase`, `NotATestDatabaseError` | the naming-convention safety guard |
| `TEST_CONNECTIONS`, `makeTestConfig` | per-dialect connection details and a filled-in `Config` |
| `deployTestSchema`, `seedTestData`, `resetTestData`, `teardownTestSchema` | fixture lifecycle |
| `isContainerRunning`, `skipIfNoContainer` | container preflight |

SQL fixtures live in `tests/fixtures/sql/<dialect>/`. `tests/integration/cli/setup.ts` spawns the built CLI for headless tests.

`tests/global-setup.ts` and `tests/global-teardown.ts` are **dead code**, left over from vitest. Nothing wires them: `bunfig.toml` sets only `preload`, and there is no vitest config in the repo. Editing them changes nothing. Real preload work belongs in `tests/preload.ts`, which pins `NOORM_CHANNEL` and strips agent-harness markers so policy-gated tests behave the same locally and in CI.
Loading