opentui is the framework used to render the tui, using react.
IMPORTANT! before starting every task ALWAYS read opentui docs with curl -s https://raw.githubusercontent.com/sst/opentui/refs/heads/main/packages/react/README.md
ALWAYS!
to use a pkg.pr.new preview URL for opentui, get the last commit hash (40 chars always) from PR anomalyco/opentui#536:
gh pr view 536 -R anomalyco/opentui --json commits --jq '.commits[-1].oid[:40]'then use it in package.json:
https://pkg.pr.new/anomalyco/opentui/@opentuah/core@<hash>
https://pkg.pr.new/anomalyco/opentui/@opentuah/react@<hash>
YOU MUST ALWAYS use the commit hash 40 characters long when changing the pkg.pr.new url! not the pr number!
if the commit hash url does not work it means it is still building. ignore the pkg.pr.new comment with the pr number in the install script. you MUST use the url with the commit hash.
NEVER run the interactive TUI (e.g. bun run src/cli.tsx without arguments). It will hang. Instead ask the user to run it.
The web command is safe to run - it generates HTML and exits:
bun run src/cli.tsx webNEVER use require. just import at the top of the file with esm
use bun add to install packages instead of npm
NEVER pass function or callbacks as dependencies of useEffect, this will very easily cause infinite loops if you forget to use useCallback
NEVER use useCallback. it is useless if we never pass functions in useEffect dependencies
Try to never use useEffect if possible. usually you can move logic directly in event handlers instead
-
if you need Node.js apis import the namesapce and not the named exports:
import fs from 'fs'and notimport { writeFileSync } from 'fs' -
DO NOT use as any. instead try to understand how to fix the types in other ways
-
to implement compound components like
List.Itemfirst define the type of List, using a interface, then use : to implement it and add compound components later using . and omitting the props types given they are already typed by the interface, here is an example -
DO NOT use console.log. only use logger.log instead
-
uses onInput not onChange. it is passed a simple string value and not an event object
-
to render examples components use renderWithProviders not render
-
ALWAYS bind all class methods to
thisin the constructor. This ensures methods work correctly when called in any context (callbacks, event handlers, etc). Example:constructor(options: Options) { // Initialize properties this.prop = options.prop // Bind all methods to this instance this.method1 = this.method1.bind(this) this.method2 = this.method2.bind(this) this.privateMethod = this.privateMethod.bind(this) }
you can use gitchamber.com to read repo files. run curl https://gitchamber.com to see how the API works. always use curl to fetch the responses of gitchamber.com
for example when working with the vercel ai sdk, you can fetch the latest docs using:
https://gitchamber.com/repos/repos/vercel/ai/main/files
use gitchamber to read the .md files using curl
you can read more examples of opentui react code using gitchamber by listing and reading files from the correct endpoint: https://gitchamber.com/repos/sst/opentui/main/files?glob=packages/react/examples/**
after any meaningful change update CHANGELOG.md with the version number and the list of changes made. in concise bullet points
in bullet points use nested list and specify for which command exactly are the changes. or group them to make it clear what they cover.
before updating the changelog bump the package.json version field first. NEVER do major bumps. NEVER publish yourself
NEVER update existing changelog bullet points for previous version unless you added those bullet points yourself recently and the change is of the same version as it is now.
-
minimize number of props. do not use props if you can use zustand state instead. the app has global zustand state that lets you get a piece of state down from the component tree by using something like
useStore(x => x.something)oruseLoaderData<typeof loader>()or even useRouteLoaderData if you are deep in the react component tree -
do not consider local state truthful when interacting with server. when interacting with the server with rpc or api calls never use state from the render function as input for the api call. this state can easily become stale or not get updated in the closure context. instead prefer using zustand
useStore.getState().stateValue. notice that useLoaderData or useParams should be fine in this case.
worker code (worker.tsx, agentation-api.ts, comments-server) is bundled by wrangler at deploy time. all their dependencies must be devDependencies in cli/package.json, not production dependencies. wrangler bundles everything into the worker output, so they don't need to be installed at runtime. production dependencies are only for the CLI tool itself (what users install with bun install -g critique).
this includes: @critique.work/server, agents, hono, stripe, resend, @critique.work/agentation, preact, and all @cloudflare/* types.
when working on the worker, agentation widget, or HTML page styles, use the preview worker to test changes before deploying to production.
deploy the preview worker from the cli/ directory:
bun run worker:deploy # deploys to preview.critique.workto upload diffs to the preview worker instead of production, set CRITIQUE_WORKER_URL:
CRITIQUE_WORKER_URL=https://preview.critique.work bun run cli/src/cli.tsx web --filter "cli/src/ansi-html.ts"this uploads to preview.critique.work so you can test HTML/CSS/widget changes without affecting production. use this whenever changing ansi-html.ts, web-utils.tsx, worker.tsx, or comments-client/.
production deploy (critique.work):
bun run worker:deploy:prodthe main cli functionality is in src/cli.tsx
the main command shows the git diff in a tui. the web command uploads the terminal output to html to domain critique.work
the command review helps the user review agents diffs, reordering and splitting diff hunks to create a progressive disclosure document with explanations and diagrams. it also supports using --web to upload the result to critique.work
this command takes a long time to run, because it uses ACP to generate the result using an LLM. so to test styles changes we will usually modify scripts/preview-review.tsx instead, which let you preview how the cli looks like. and passing --web to that script to also preview how the website looks like
Source code for dependencies is available in opensrc/ for deeper understanding of implementation details.
See opensrc/sources.json for the list of available packages and their versions.
Use this source code when you need to understand how a package works internally, not just its types/interface.
To fetch source code for a package or repository you need to understand, run:
npx opensrc <package> # npm package (e.g., npx opensrc zod)
npx opensrc pypi:<package> # Python package (e.g., npx opensrc pypi:requests)
npx opensrc crates:<package> # Rust crate (e.g., npx opensrc crates:serde)
npx opensrc <owner>/<repo> # GitHub repo (e.g., npx opensrc vercel/ai)we are using an opentui fork published as @opentuah. imports use @opentuah/core and @opentuah/react directly (no npm alias remapping).
ALWAYS keep @opentuah/core and @opentuah/react versions pinned to exact versions in package.json (no ^ or ~ prefix). When updating, use bun add @opentuah/core@x.y.z @opentuah/react@x.y.z with the exact version number.
To find my opentui folder with that fork see kimaki projects via kimaki cli, the one named opentui.
To apply fixes there you must create a new branch and then merge it in the branch called opentuah. then publish and update the versions here. to publish there is a script specifically for opentuah.