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
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Keep LF line endings in the working tree on every platform
# (Windows runners default to core.autocrlf=true, which would break
# prettier's LF expectation).
* text=auto eol=lf
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI

on:
workflow_dispatch:
push:
branches: [main]
pull_request:

jobs:
check:
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [22, 24]
steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: ${{ matrix.node-version }}
cache: npm

- name: Install dependencies
run: npm ci

- name: Check formatting
run: npm run format:check

- name: Lint
run: npm run lint

- name: Typecheck
run: npm run typecheck

- name: Test
run: npm test

- name: Build
run: npm run build

- name: Smoke test built CLIs
run: |
node dist/ppv-cli.js --help
node dist/ppex.js --help
node dist/pptop.js --help
108 changes: 108 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,linux
# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,linux

### Linux ###
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux

# Node dependencies
node_modules/

# Build outputs
dist/
build/

# TypeScript
*.tsbuildinfo
*.d.ts
*.d.ts.map
*.js
*.js.map

# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# Test
coverage/

# Local environment (contains the API token)
.env
.env.*

# npm pack artifacts
*.tgz
13 changes: 13 additions & 0 deletions .markdownlint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
MD007:
indent: 4

MD013:
line_length: 500

MD025:
front_matter_title: ''
level: 1

MD033:
allowed_elements:
- br
15 changes: 15 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Build outputs
build/
dist/
coverage/

# Dependencies
node_modules/

# Snapshot data (large generated JSON)
snapshots/

# Misc
package-lock.json
renovate.json

10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!-- markdownlint-disable MD024 -->

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
95 changes: 95 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Development

Notes for developing ppv26-cli. See [README.md](README.md) for setup and usage, and [PRD.md](PRD.md) for requirements and design.

## Commands

```console
npm run typecheck # tsc --noEmit
npm test # vitest
npm run lint # eslint (lint:fix to autofix)
npm run format # prettier --write (format:check to check only)
npm run build # build:tsc then build:bundle -> dist/
```

`npm install` also builds `dist/` automatically (via the `prepare` script), so a fresh clone is runnable right away.

CI runs all of the above (formatting as a check) plus a `--help` smoke test of the built CLI on ubuntu / windows with Node 22 / 24.

## Running the binaries

There are two ways to run the binaries; for pptop / ppex the two ways use different React builds. This matters:

```console
# From source (fast iteration). Uses React's DEVELOPMENT build.
npx tsx src/pptop.tsx
npx tsx src/ppex.tsx
npm run ppv-cli -- <cmd>

# From dist/ (production-equivalent). Uses React's PRODUCTION build.
npm run build && node dist/pptop
node dist/ppex
node dist/ppv-cli
```

Why the split: Ink picks React's dev vs production build at runtime from `process.env.NODE_ENV`, and neither `tsx` nor `tsc` replaces that value, so running from source (or a plain `tsc` output) gets the **dev** build.
React 19's dev build carries the Component Performance Track, which serializes changed props into `performance.measure` entries that Node's performance timeline retains indefinitely. When a large array prop changes reference every render (pptop sort / search recomputing `filtered`), that grows without bound and the process eventually OOMs (issue #18).

`npm run build` runs two stages (`package.json` scripts):

- `build:tsc` - `tsc` emits `ppv-cli` and the shared React-free `dist/core/` (no Ink/ui output).
- `build:bundle` - `scripts/build-ink.mjs` bundles pptop / ppex with esbuild, baking `NODE_ENV=production` (via `define`) so React's production build is selected at build time and the dev-only instrumentation is dead-code-eliminated. This is what makes the shipped binaries safe from the OOM above. `ppv-cli` (no React) stays on `tsc`. Use `npm run build:bundle` alone to rebuild just the bundles.

Practical guidance:

- UI work / quick checks -> `npx tsx src/pptop.tsx` is fine. Just avoid hammering sort / search over a large snapshot; the dev build leaks.
- Memory or production-fidelity checks -> run the built binary with `npm run build && node dist/pptop`, or prefix the source run with `NODE_ENV=production npx tsx src/pptop.tsx` (which also disables the instrumentation).

## Source layout

One binary = one entry file at the src/ root (named after the bin) plus one directory of the same name:

```text
src/
ppv-cli.ts ppex.tsx pptop.tsx # bin entries (thin launchers)
ppc/ # ppv-cli-only (commands/, explorer.ts)
ppex/ # ppex-only (app container, screens)
pptop/ # pptop-only (app container, header)
ui/ # shared Ink components
core/ # shared React-free logic
```

Rules:

- Shared code has exactly two homes: `ui/` for anything that depends on Ink (React), `core/` for everything else.
- Dependencies point one way: `pp*/ -> ui/ -> core/`. One-shot binaries (ppv-cli) must not import from `ui/`, so starting the CLI never loads Ink/React.
- Tests are co-located: `foo.ts` is tested by `foo.test.ts` right next to it (excluded from the build by tsconfig.build.json), so tests move together with their subjects when directories are reorganized.

To add a new binary `ppxxx`:

1. Create `src/ppxxx.tsx` (arg parsing, TTY guard, render) and `src/ppxxx/` (the app). The existing three are the templates.
2. Add `"ppxxx": "./dist/ppxxx.js"` to `bin` in package.json.
3. Document it in README.md and PRD.md.

## Dependencies management

Check for updates with:

```console
npx npm-check-updates --format group
```

Policy:

- **Patch / minor updates**: apply freely. Run the full check suite (format:check, lint, typecheck, test, build, and a smoke run of `dist/ppv-cli.js`) before committing.
- **Major updates**: review the changelog first, apply one package at a time, and verify with the full check suite.

### Constraints

Deliberate exceptions to "just update". Do not bump these without revisiting the reasoning:

| Package | Constraint | Reason |
| ---------------------------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `typescript` | Stay on 5.x | TypeScript 6/7 is the native-compiler (tsgo) generation, not a routine upgrade. Wait until the toolchain (typescript-eslint, tsx) and the PROMIDAS ecosystem (all repos on 5.9) have migrated, then follow. (Decided 2026-07-15) |
| `@types/node` | Stay on 24.x | Type definitions should not exceed the supported/tested Node versions (engines: >=22, CI matrix: 22 / 24). Newer majors would let APIs unavailable on Node 22 pass the typecheck. Raise only together with the engines / CI matrix. |
| `promidas` / `promidas-utils` / `protopedia-api-v2-client` | Update promptly, together | PROMIDAS ecosystem packages. Keep `promidas` and `protopedia-api-v2-client` on versions that satisfy the peerDependency (`^3`). This CLI dogfoods the ecosystem, so new releases should be adopted early. |
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 F88

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Loading