diff --git a/dot_agents/skills/blume-migrate/SKILL.md b/dot_agents/skills/blume-migrate/SKILL.md index ab701ce..e50d0cf 100644 --- a/dot_agents/skills/blume-migrate/SKILL.md +++ b/dot_agents/skills/blume-migrate/SKILL.md @@ -44,8 +44,8 @@ The single biggest shift for most sources — especially Mintlify — is that ** - **Folders become groups, files become pages.** A page's sidebar label is its frontmatter `title`; a group's label is the humanized folder name. - **Ordering resolves highest-priority-first:** an explicit `navigation.sidebar` (replaces the whole tree) → a folder's `meta.ts` `pages` array → a page's frontmatter `sidebar.order` → the filesystem (`index` first, then numeric filename prefix like `01-`, then alphabetical). -- **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order. -- **Sidebar render mode is global, not per-folder.** `navigation.sidebar.display` in `blume.config.ts` is `"flat"` (default), `"group"` (collapsible), or `"page"` (drill-in sub-panel) and applies to **every** group at once. (It used to live on each folder's `meta.ts` as `display`; that field was **removed** — writing it in a `meta.ts` is now a build error. Set it once in config instead.) An explicit `navigation.sidebar` item may still override its own group's `display`. +- **`meta.ts` refines one folder** (`defineMeta({ title, icon, order, collapsed, pages, display })`). The `pages` array lists children by slug (numeric prefix and parentheses stripped); children you omit fall back to their own `sidebar.order`, then filesystem order (`index` still sorts first) — so a partial `pages` list is safe, but list every child when the source declared a complete order. +- **Sidebar render mode: a global default with per-folder overrides.** `navigation.sidebar.display` in `blume.config.ts` is `"flat"` (default), `"group"` (collapsible), or `"page"` (drill-in sub-panel) and sets the mode for every group at once. A folder can override its **own** group: `display` in its `meta.ts`, or — sugar when the folder has an `index` page — `sidebar.display` in that index page's frontmatter. Precedence: index frontmatter → `meta.ts` → global config → `flat`; an override applies to that one group only (nested subgroups resolve their own chain). So a source's per-category collapse/drill-in modes migrate per folder — only reach for the global mode when the whole sidebar changes. Under an explicit `navigation.sidebar`, the config item's own `display` field is the only per-group control (frontmatter/meta `display` is ignored there, with a `BLUME_SIDEBAR_DISPLAY_IGNORED` warning). - **An explicit `navigation.sidebar` replaces filesystem generation entirely.** Use it only for a nav shape files can't express. Its items are a page route string, a group (`{ label, items }`), or a link (`{ label, href }`). - **Config-declared nesting has no on-disk counterpart — materialize it or it flattens silently.** When a source (Mintlify `groups`, Nextra `_meta`, a Docusaurus sidebar…) declares a nested group, its pages usually sit **flat in one folder** and the grouping lives only in config. Filesystem-derived nav sees the flat folder and drops the inner group. To keep the nesting you must **either** move those pages into a real subfolder (`meta.ts` for label/`collapsed`) — which changes their URLs, so add `redirects` — **or** declare the group in an explicit `navigation.sidebar`, which nests the existing routes without moving a file. Walk config `pages`/nav arrays **recursively** during inventory and record where config nesting depth exceeds on-disk depth; that gap is exactly what gets lost. diff --git a/dot_agents/skills/blume-migrate/references/docusaurus.md b/dot_agents/skills/blume-migrate/references/docusaurus.md index e144040..23843bd 100644 --- a/dot_agents/skills/blume-migrate/references/docusaurus.md +++ b/dot_agents/skills/blume-migrate/references/docusaurus.md @@ -41,7 +41,7 @@ Every Docusaurus repo serves **`static/`** at the site root (`static/img/foo.png ## Navigation: `sidebars.js` + `_category_.json` - **Autogenerated sidebar** (`{ type: 'autogenerated', dirName: '...' }`) → Blume's default filesystem navigation. Docusaurus strips numeric prefixes (`01-`) exactly like Blume, so the convention round-trips; no config needed. -- **Explicit sidebar** (arrays of doc IDs, categories, links) → restructure into folders where possible; use `navigation.sidebar` only for shapes files can't express. A category `{ type: 'category', label, items }` → a folder (label → `meta.ts` `title`); `collapsed` → `meta.ts` `collapsed` (**no `display` field in `meta.ts`** — if collapse matters, set the global `navigation.sidebar.display: "group"` once in config). +- **Explicit sidebar** (arrays of doc IDs, categories, links) → restructure into folders where possible; use `navigation.sidebar` only for shapes files can't express. A category `{ type: 'category', label, items }` → a folder (label → `meta.ts` `title`); `collapsed` → `meta.ts` `collapsed`, and collapsible rendering → that folder's `meta.ts` `display: "group"` (or `navigation.sidebar.display: "group"` once in config when every category collapses). - **`_category_.json`** (also `.yml`) (`{ label, position, collapsed, collapsible, link, className, customProps }`) → a folder `meta.ts`: `label`→`title`, `position`→`order`, `collapsed`→`collapsed`. `link.type: 'generated-index'` → an `index` page in the folder — **and the old URL was `/docs/category/`**, so add a redirect and rewrite inbound links. `link.type: 'doc'` → make that doc the folder's `index`. `collapsible`/`className`/`customProps` → drop (report). - **`src/pages/` — inventory, don't nuke.** Nearly every repo has a React landing page (`src/pages/index.tsx`) and often extra Markdown pages. Markdown pages → content pages; the React home page → rebuild as a docs index or a custom `.astro` page under `content.pages` — report either way. diff --git a/dot_agents/skills/blume-migrate/references/fumadocs.md b/dot_agents/skills/blume-migrate/references/fumadocs.md index 8c4ad22..351ea07 100644 --- a/dot_agents/skills/blume-migrate/references/fumadocs.md +++ b/dot_agents/skills/blume-migrate/references/fumadocs.md @@ -39,7 +39,7 @@ Everything else is `defineConfig({ title })`. | `description` | **drop** (folders have no description) | | `collapsible: false` | **drop** (report — no per-folder equivalent) | -`meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages` — there is **no `display` field** (writing one is a build error). Collapse rendering is global: when any folder needs collapsing, set `navigation.sidebar.display: "group"` once in `blume.config.ts`. +`meta.ts` accepts **only** `title`, `icon`, `order`, `collapsed`, `pages`, `display`. Render mode is per-folder or global: a folder that needs collapsible rendering sets its own `meta.ts` `display: "group"` (drill-in is `"page"`); when the whole sidebar should collapse, set `navigation.sidebar.display: "group"` once in `blume.config.ts` instead of repeating it per folder. **`root: true` folders are Fumadocs' tab mechanism** — Fumadocs UI renders them as layout tabs and scopes the sidebar to the active one. That is exactly Blume's `navigation.tabs`: add `{ label, path, icon? }` per root folder (label/icon from its `meta.json`), pointing at the folder's route. Blume then scopes the sidebar by URL prefix the same way. Don't try to model root folders inside `meta.ts`. diff --git a/dot_agents/skills/blume-migrate/references/mintlify.md b/dot_agents/skills/blume-migrate/references/mintlify.md index be73689..553c313 100644 --- a/dot_agents/skills/blume-migrate/references/mintlify.md +++ b/dot_agents/skills/blume-migrate/references/mintlify.md @@ -83,7 +83,7 @@ The codemod touches **only frontmatter**. Icons in MDX **body** (` - Target workspace (uses credentials) - -t, --title - Document title (required) - -c, --content <content> - Markdown content (inline) - -f, --content-file <path> - Read content from file - --project <project> - Attach to project (UUID, slug ID, or name) - --issue <issue> - Attach to issue (identifier like TC-123) - --icon <icon> - Document icon (emoji) - -i, --interactive - Interactive mode with prompts + -h, --help - Show this help. + --workspace <slug> - Target workspace (uses credentials) + -t, --title <title> - Document title (required) + -c, --content <content> - Markdown content (inline) + -f, --content-file <path> - Read content from file + --project <project> - Attach to project (UUID, slug ID, or name) + --issue <issue> - Attach to issue (identifier like TC-123) + --initiative <initiative> - Attach to initiative (UUID, slug ID, or name) + --team <team> - Attach to team (key); with --cycle, scopes the cycle lookup instead + --cycle <cycle> - Attach to cycle: name, number, 'active'/'now', 'next', 'previous', or a relative + offset like +1 (team from --team or config) + --release <release> - Attach to release (UUID, name, or version) + --icon <icon> - Document icon (emoji) + -i, --interactive - Interactive mode with prompts ``` ### delete @@ -85,12 +90,17 @@ Description: Options: - -h, --help - Show this help. - --workspace <slug> - Target workspace (uses credentials) - --project <project> - Filter by project (slug or name) - --issue <issue> - Filter by issue (identifier like TC-123) - --json - Output as JSON - --limit <limit> - Limit results (Default: 50) + -h, --help - Show this help. + --workspace <slug> - Target workspace (uses credentials) + --project <project> - Filter by project (UUID, slug ID, or name) + --issue <issue> - Filter by issue (identifier like TC-123) + --initiative <initiative> - Filter by initiative (UUID, slug ID, or name) + --team <team> - Filter by team (key); with --cycle, scopes the cycle lookup instead + --cycle <cycle> - Filter by cycle: name, number, 'active'/'now', 'next', 'previous', or a relative + offset like +1 (team from --team or config) + --release <release> - Filter by release (UUID, name, or version) + --json - Output as JSON + --limit <limit> - Limit results (Default: 50) ``` ### update @@ -106,15 +116,21 @@ Description: Options: - -h, --help - Show this help. - --workspace <slug> - Target workspace (uses credentials) - -t, --title <title> - New title for the document - -c, --content <content> - New markdown content (inline) - -f, --content-file <path> - Read new content from file - --icon <icon> - New icon (emoji) - --project <project> - Attach to project (UUID, slug ID, or name) - -e, --edit - Open current content in $EDITOR for editing - --force - Update content even when document comments may lose inline anchors + -h, --help - Show this help. + --workspace <slug> - Target workspace (uses credentials) + -t, --title <title> - New title for the document + -c, --content <content> - New markdown content (inline) + -f, --content-file <path> - Read new content from file + --icon <icon> - New icon (emoji) + --project <project> - Re-point to project (UUID, slug ID, or name); replaces the current attachment + --issue <issue> - Re-point to issue (identifier like TC-123); replaces the current attachment + --initiative <initiative> - Re-point to initiative (UUID, slug ID, or name); replaces the current attachment + --team <team> - Re-point to team (key); with --cycle, scopes the cycle lookup instead + --cycle <cycle> - Re-point to cycle: name, number, 'active'/'now', 'next', 'previous', or a + relative offset like +1 (team from --team or config) + --release <release> - Re-point to release (UUID, name, or version); replaces the current attachment + -e, --edit - Open current content in $EDITOR for editing + --force - Update content even when document comments may lose inline anchors ``` ### view diff --git a/dot_agents/skills/remix/SKILL.md b/dot_agents/skills/remix/SKILL.md index 8f6aac2..738f139 100644 --- a/dot_agents/skills/remix/SKILL.md +++ b/dot_agents/skills/remix/SKILL.md @@ -84,19 +84,19 @@ Use these root directories consistently: - `app/` for runtime application code - `db/` for migrations and local database files -- `public/` for static assets served as-is +- root `public/` for static assets served as-is from the app root - `test/` for shared helpers, fixtures, and integration coverage - `tmp/` for uploads, caches, local session files, and other scratch data Inside `app/`, organize by responsibility: -- `assets/` for client entrypoints and client-owned browser behavior - `actions/` for controller-owned route handlers, route-local response rendering, and route-local UI/helpers that are not shared across route areas - `data/` for schema, queries, persistence setup, migrations, and runtime data initialization - `middleware/` for request lifecycle concerns such as auth, sessions, uploads, and database injection +- `public/` directories inside the narrowest owner for browser-reachable source code, with the browser runtime entrypoint at `app/actions/public/entry.ts` - `ui/` for shared cross-route UI primitives - `utils/` only for genuinely cross-layer helpers that do not clearly belong elsewhere -- `routes.ts` for the route contract +- `routes.ts` for the shared server-and-browser route contract and type-safe href generation - `router.ts` for router setup and wiring ### Placement Precedence @@ -148,10 +148,11 @@ When code could live in multiple places: - Model HTTP behavior explicitly. Status codes, headers, redirects, cache rules, and content types are part of the route contract - Make the server route correct first. A POST should already return the right HTML, redirect, or error response on its own before `clientEntry(...)` layers interactivity on top - Validate input at the boundary using `remix/data-schema` (and `remix/data-schema/form-data` for forms). `parseSafe` makes the failure path a return value instead of an exception -- Derive `AppContext` from the middleware stack so `get(Database)`, `get(Session)`, `get(Auth)`, and similar keys stay typed. If the controller never reads from context, it doesn't need the harness +- Derive `AppContext` from the middleware stack so `get(databaseContext)`, `get(Session)`, `get(Auth)`, and similar keys stay typed. If the controller never reads from context, it doesn't need the harness - Outside actions and controllers, only use `getContext()` when `asyncContext()` is in the middleware stack - Remix Component is not React: write `function Name(handle: Handle<Props>) { return () => ... }`, read props from `handle.props`, keep state in setup-scope variables, call `handle.update()` explicitly, and do DOM-sensitive work in event handlers or `queueTask(...)`, not in render - Prefer host-element mixins via `mix={mixin(...)}` for behavior and styling instead of inventing custom host prop conventions. Use `mix={[...]}` only when composing multiple mixins +- Keep short, one-off static styles inline with `mix={css(...)}`. Extract a module-scoped style descriptor when it forms a reused visual recipe, has substantial selectors, media queries, or keyframes, or is large enough to obscure the component. Export a style descriptor only after multiple modules need the same visual recipe; otherwise keep it with its narrowest owner - Hydrated `clientEntry(...)` props must be serializable. Do not pass functions, class instances, or opaque runtime objects ## Security And Session Defaults @@ -227,12 +228,12 @@ Use this map to find the right package quickly. Each entry says what the package - `remix/data-schema/coerce` — coercion helpers for strings, numbers, booleans, dates, and ids. Use when input arrives as a string but should be a typed value - `remix/data-schema/form-data` — `f.object` and `f.field` for parsing `FormData` directly. Use in actions that read browser forms - `remix/data-schema/lazy` — recursive or mutually-referential schemas. Use when a schema needs to refer to itself or another schema that is declared later -- `remix/data-table` — typed tables and a `Database` interface. Use for `table`, `column`, `createDatabase` when modeling persisted data -- `remix/data-table/sqlite`, `remix/data-table/postgres`, `remix/data-table/mysql` — adapters. Use to back `createDatabase` with a real engine. SQLite accepts Node, Bun, and compatible synchronous clients with the shared `prepare`/`exec` surface -- `remix/data-table/migrations` — migration authoring and runners. Use for `createMigration`, `createMigrationRunner` +- `remix/data-table` — typed tables and the shared `Database` API. Use `table` and `column` when modeling persisted data, then create a concrete database from the matching dialect package. Integration packages can implement `DatabaseDriver` and extend `Database` to add another SQL dialect +- `remix/data-table/sqlite`, `remix/data-table/postgres`, `remix/data-table/mysql` — concrete database integrations. Use `createSqliteDatabase`, `createPostgresDatabase`, or `createMysqlDatabase`. SQLite accepts Node, Bun, and compatible synchronous clients with the shared `prepare`/`exec` surface +- `remix/data-table/migrations` — migration authoring and registries. Use for `createMigration` and `createMigrationRegistry`; run migrations with `Database.migrate()` - `remix/data-table/migrations/node` — `loadMigrations` from disk. Use in startup scripts that apply migrations - `remix/data-table/operators` — query operators such as `inList(...)`. Use when `where` clauses need set or comparison logic -- `remix/data-table/sql-helpers` — SQL helper utilities for adapter or advanced query work. Avoid this in normal app code unless you are intentionally working below the table/query API +- `remix/data-table/sql-helpers` — SQL helper utilities for database integrations or advanced query work. Avoid this in normal app code unless you are intentionally working below the table/query API ### Auth, Sessions, and Cookies @@ -318,17 +319,18 @@ export const routes = route({ ```typescript import { createController } from 'remix/router' +import { databaseContext } from '../middleware/database.ts' import { routes } from '../routes.ts' export default createController(routes.books, { actions: { async index({ get }) { - let db = get(Database) + let db = get(databaseContext) let allBooks = await db.findMany(books, { orderBy: ['id', 'asc'] }) return render(<BooksIndexPage allBooks={allBooks} />) }, async show({ get, params }) { - let db = get(Database) + let db = get(databaseContext) let book = await db.findOne(books, { where: { slug: params.slug } }) if (!book) return new Response('Not Found', { status: 404 }) return render(<BookShowPage book={book} />) @@ -393,8 +395,8 @@ import { redirect } from 'remix/response/redirect' import * as s from 'remix/data-schema' import * as f from 'remix/data-schema/form-data' import { Session } from 'remix/session' -import { Database } from 'remix/data-table' +import { databaseContext } from '../middleware/database.ts' import { routes } from '../routes.ts' let bookSchema = f.object({ @@ -410,7 +412,7 @@ export default createController(routes.books, { return render(<NewBookPage errors={parsed.issues} />, { status: 400 }) } - let db = get(Database) + let db = get(databaseContext) let book = await db.create(books, parsed.value) let session = get(Session) diff --git a/dot_agents/skills/remix/references/assets-and-browser-modules.md b/dot_agents/skills/remix/references/assets-and-browser-modules.md index f5e6934..8178309 100644 --- a/dot_agents/skills/remix/references/assets-and-browser-modules.md +++ b/dot_agents/skills/remix/references/assets-and-browser-modules.md @@ -8,13 +8,13 @@ How to serve browser scripts and styles from source. Read this when the task inv - Choosing between `staticFiles()` for already-built files and `createAssetServer()` for source assets that need import rewriting, preloads, or fingerprinted URLs - Generating script URLs or `<link rel="modulepreload">` tags for a client entry - Enabling browser HMR for source-served modules -- Keeping server-only files out of the browser via `denyFiles` rules +- Keeping files such as tests out of the browser via `denyFiles` rules For routing the URL namespace itself, see `routing-and-controllers.md`. For client entry hydration and browser update handling, see `hydration-frames-navigation.md`. For the Node HMR runner and browser HMR channel, see `middleware-and-server.md`. ## When To Reach For It -Use `remix/assets` when the app serves browser JavaScript, TypeScript, or CSS from source files. This is the right tool for client entrypoints, browser-only helpers, styles under `app/assets/`, and monorepo code that should be compiled and served under a public URL namespace. +Use `remix/assets` when the app serves browser JavaScript, TypeScript, or CSS from source files. This is the right tool for client entrypoints, browser-only helpers, styles, and monorepo code that should be compiled and served under a public URL namespace. Use `staticFiles()` for files that already exist on disk exactly as they should be served. Use `createAssetServer()` for source scripts or styles that need rewriting, dependency scanning, preloads, sourcemaps, or fingerprinted URLs. @@ -36,9 +36,9 @@ let assetServer = createAssetServer({ 'app/*path': 'app/*path', 'node_modules/*path': 'node_modules/*path', }, - allowFiles: ['app/assets/**'], + allowFiles: ['app/routes.ts', 'app/**/public/**'], allowPackages: ['remix'], - denyFiles: ['app/**/*.server.*'], + denyFiles: ['app/**/*.test.*'], target: { es: '2020', chrome: '109', safari: '16.4' }, sourceMaps: process.env.NODE_ENV === 'development' ? 'external' : undefined, minify: process.env.NODE_ENV === 'production', @@ -61,10 +61,12 @@ export default createController(routes, { ## Rules - Treat `allowFiles`/`allowPackages` and `denyFiles` as the security boundary for browser-reachable source files. +- Put browser-reachable app source in a `public/` directory inside `app/`, beside its narrowest owner, such as `app/ui/public/` or `app/actions/cart/public/`. +- Every local dependency in a browser module graph must match `allowFiles`, so keep the whole graph inside those `public/` directories. `app/routes.ts` is allowed separately so browser modules can build type-safe links with `routes.*.href(...)`. +- Deny test modules with `denyFiles` so tests can be colocated inside a `public/` directory without becoming browser-reachable. - Use `allowFiles` and `denyFiles` for file paths and globs. Relative values resolve from `rootDir`. - Use `allowPackages` for exact package names, not globs or subpaths. Packages allowed by `allowPackages` also allow their installed `dependencies` and `optionalDependencies`; peer dependencies must be listed explicitly if they should be browser-reachable. - `denyFiles` takes precedence over both file and package allow rules. -- Add `denyFiles` rules for server-only modules such as `*.server.*`, private config, or other files that should never be exposed. - Set `rootDir` explicitly in monorepos so relative paths resolve from the intended project root. - `basePath` is the public URL namespace handled by the asset server. - `fileMap` keys are URL patterns relative to `basePath`, and values are root-relative file path patterns. They use `route-pattern` syntax on both sides. @@ -76,8 +78,8 @@ export default createController(routes, { Use `getHref()` when you need the public URL for one module, and `getPreloads()` when you want `<link rel="modulepreload">` tags or `Link` headers for one or more entrypoints and their dependencies. ```typescript -let entryHref = await assetServer.getHref('app/assets/entry.ts') -let preloads = await assetServer.getPreloads(['app/assets/entry.ts']) +let entryHref = await assetServer.getHref('app/actions/public/entry.ts') +let entryPreloads = await assetServer.getPreloads('app/actions/public/entry.ts') ``` Use this when rendering documents or layouts that boot browser behavior with a known client entry. @@ -116,7 +118,8 @@ const isHmr = Boolean(isDevelopment && process.env.REMIX_NODE_HMR) const assetServer = createAssetServer({ basePath: '/assets', fileMap: { '/app/*path': 'app/*path' }, - allowFiles: ['app/assets/**'], + allowFiles: ['app/routes.ts', 'app/**/public/**'], + denyFiles: ['app/**/*.test.*'], watch: isDevelopment, hmr: isHmr ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel() diff --git a/dot_agents/skills/remix/references/auth-and-sessions.md b/dot_agents/skills/remix/references/auth-and-sessions.md index f201cab..db94df0 100644 --- a/dot_agents/skills/remix/references/auth-and-sessions.md +++ b/dot_agents/skills/remix/references/auth-and-sessions.md @@ -137,7 +137,7 @@ Notice that there is no manual `Set-Cookie` plumbing in the action — the sessi ```typescript import { auth, createSessionAuthScheme } from 'remix/middleware/auth' import { Session } from 'remix/session' -import { Database } from 'remix/data-table' +import { databaseContext } from '~/middleware/database.ts' export function loadAuth() { return auth({ @@ -148,7 +148,7 @@ export function loadAuth() { return data ?? null }, async verify(value, context) { - let db = context.get(Database) + let db = context.get(databaseContext) return (await db.find(users, value.userId)) ?? null }, invalidate(session) { @@ -195,7 +195,7 @@ export let passwordProvider = createCredentialsAuthProvider({ return s.parse(loginSchema, formData) }, async verify({ email, password }, context) { - let db = context.get(Database) + let db = context.get(databaseContext) let user = await db.findOne(users, { where: { email } }) if (!user || !(await verifyPassword(password, user.password_hash))) { return null @@ -300,7 +300,7 @@ export default createController(routes.auth.google, { async callback(context) { let { result, returnTo } = await finishExternalAuth(googleProvider, context) - let db = context.get(Database) + let db = context.get(databaseContext) let { user, authAccount } = await resolveExternalAuth(db, result) let session = completeAuth(context) @@ -322,7 +322,7 @@ Use `refreshExternalAuth(provider, tokens)` when an app has stored OAuth/OIDC to ```typescript async function refreshGoogleTokens({ get }) { - let db = get(Database) + let db = get(databaseContext) let account = await db.findOne(authAccounts, { where: { provider: 'google' } }) if (!account) return null diff --git a/dot_agents/skills/remix/references/data-and-validation.md b/dot_agents/skills/remix/references/data-and-validation.md index bfe8425..c298cd1 100644 --- a/dot_agents/skills/remix/references/data-and-validation.md +++ b/dot_agents/skills/remix/references/data-and-validation.md @@ -113,30 +113,30 @@ export const books = table({ ## Database Setup -Create a database with an adapter and expose it via middleware: +Create a database and expose it via middleware: ```typescript import BetterSqlite3 from 'better-sqlite3' -import { createDatabase, Database } from 'remix/data-table' -import { createSqliteDatabaseAdapter } from 'remix/data-table/sqlite' +import { createSqliteDatabase } from 'remix/data-table/sqlite' let sqlite = new BetterSqlite3('./db/app.db') sqlite.pragma('foreign_keys = ON') -let adapter = createSqliteDatabaseAdapter(sqlite) -export let db = createDatabase(adapter) +export let db = createSqliteDatabase(sqlite) ``` -`createSqliteDatabaseAdapter` accepts synchronous SQLite clients with a shared `prepare`/`exec` surface, including Node's `node:sqlite`, Bun's `bun:sqlite`, and compatible clients. Use whichever client fits the runtime instead of assuming `better-sqlite3` is required. +`createSqliteDatabase` accepts synchronous SQLite clients with a shared `prepare`/`exec` surface, including Node's `node:sqlite`, Bun's `bun:sqlite`, and compatible clients. Use whichever client fits the runtime instead of assuming `better-sqlite3` is required. ### Database middleware ```typescript -import type { Middleware } from 'remix/router' -import { Database } from 'remix/data-table' +import type { Database } from 'remix/data-table' +import { createContextKey, type Middleware } from 'remix/router' + +export const databaseContext = createContextKey<Database>() export function loadDatabase(): Middleware { return async (context, next) => { - context.set(Database, db) + context.set(databaseContext, db) return next() } } @@ -145,7 +145,7 @@ export function loadDatabase(): Middleware { ### Querying ```typescript -let db = get(Database) +let db = get(databaseContext) // Find by primary key let book = await db.find(books, id) @@ -220,7 +220,7 @@ Do **not** import app code (e.g. `app/data/schema.ts`) into migration files. Mig ### Transaction modes -Migrations run inside a transaction by default (when the adapter supports transactional DDL). Override per migration with a directive comment in `up.sql`: +Migrations run inside a transaction by default (when the database supports transactional DDL). Override per migration with a directive comment in `up.sql`: ```sql -- data-table/transaction: none @@ -232,15 +232,15 @@ Modes: `auto` (default — wrap when supported), `required` (wrap; throw if unsu ### Running migrations ```typescript -import { createMigrationRunner } from 'remix/data-table/migrations' import { loadMigrations } from 'remix/data-table/migrations/node' let migrations = await loadMigrations('./db/migrations') -let runner = createMigrationRunner(adapter, migrations) -await runner.up() +await db.migrate(migrations) ``` -The runner checksums each `up.sql` and detects drift if a previously applied migration changes. Use `runner.status()` to inspect applied/pending/drifted state, and `runner.down()` to revert. +The database checksums each `up.sql` and detects drift if a previously applied migration changes. +Use `db.migrationStatus(migrations)` to inspect applied/pending/drifted state, and +`db.migrate(migrations, { direction: 'down' })` to revert. ## Input Validation (`remix/data-schema`) diff --git a/dot_agents/skills/remix/references/hydration-frames-navigation.md b/dot_agents/skills/remix/references/hydration-frames-navigation.md index f62e134..f438f33 100644 --- a/dot_agents/skills/remix/references/hydration-frames-navigation.md +++ b/dot_agents/skills/remix/references/hydration-frames-navigation.md @@ -95,6 +95,7 @@ Client entry props must be serializable: strings, numbers, booleans, `null`, `un Use `run` to start the client runtime. It scans the document for client entry markers, loads modules, and hydrates each one: ```tsx +import type { ResolveFrameOptions } from 'remix/ui' import { run } from 'remix/ui' const app = run({ @@ -102,14 +103,31 @@ const app = run({ let mod = await import(moduleUrl) return mod[exportName] }, - async resolveFrame(src, signal, target) { - let headers = new Headers({ accept: 'text/html' }) - if (target) headers.set('x-remix-target', target) - let response = await fetch(src, { headers, signal }) + async resolveFrame(src, options) { + let headers = new Headers({ accept: 'text/html', 'x-remix-frame': 'true' }) + if (options?.target) headers.set('x-remix-target', options.target) + let response = await fetch(src, { + body: getRequestBody(options), + headers, + method: options?.method, + signal: options?.signal, + }) return response.body ?? (await response.text()) }, }) +function getRequestBody(options?: ResolveFrameOptions): BodyInit | undefined { + let formData = options?.formData + if (!formData) return + if (options.encType !== 'application/x-www-form-urlencoded') return formData + + let body = new URLSearchParams() + for (let [name, value] of formData) { + body.append(name, typeof value === 'string' ? value : value.name) + } + return body +} + app.addEventListener('error', (event) => { console.error('Component error:', event.error) }) @@ -120,7 +138,7 @@ await app.ready() ### `run` options - **`loadModule(moduleUrl, exportName)`** (required) — return the component function for each client entry. Typically uses dynamic `import()`. -- **`resolveFrame(src, signal, target)`** (optional) — called when a `<Frame>` loads or reloads content. `target` is available when frame targeting matters. +- **`resolveFrame(src, options)`** (optional) — called when a `<Frame>` loads or reloads content and for intercepted link and form navigations. `options` may contain `signal` and `target`; non-GET forms also provide `formData`, `method`, and `encType`. ### `app` methods @@ -189,6 +207,20 @@ handle.frames.top.reload() When a frame reloads, matching DOM nodes are updated in place. Client entries receive updated props while preserving their local component state. +### Form navigation + +When `run({ resolveFrame })` is active, eligible same-origin forms progressively enhance into frame navigations. Native validation and the form's `submit` event still run first. + +- Forms target `handle.frames.top` by default. +- `rmx-target` selects a named frame. +- `rmx-src` selects a different frame request URL while preserving the form action as the navigation destination. +- `rmx-history="push|replace"` overrides how the navigation updates history. +- `rmx-reset-scroll="false"` preserves scroll position. +- `rmx-document` opts back into a document submission. +- Cross-origin forms, `method="dialog"`, and `target="_blank"` remain browser-owned. + +GET controls are already encoded in `src`, so GET forms reach the resolver like links. Non-GET forms provide their native `FormData`, effective method, and encoding. The resolver owns body encoding and method-override conventions. Non-GET submissions to the current URL replace its history entry; GET submissions and submissions to a different URL push one. The `rmx-history` attribute overrides those defaults. + ### Nested frames Frames can nest. Each frame owns its own DOM region and hydrates client entries independently. During SSR, `handle.frame.src` points at the frame being rendered, while `handle.frames.top.src` stays fixed at the outer document URL. @@ -252,7 +284,7 @@ navigate('/dashboard', { history: 'replace' }) Options: `src`, `target`, `history` (`'push' | 'replace'`), `resetScroll`. -Attributes understood by the runtime: `rmx-target`, `rmx-src`, `rmx-document`. +Attributes understood by the runtime: `rmx-target`, `rmx-src`, `rmx-history`, `rmx-reset-scroll`, `rmx-document`. ## Head Management diff --git a/dot_agents/skills/remix/references/middleware-and-server.md b/dot_agents/skills/remix/references/middleware-and-server.md index 230c8f2..af714a9 100644 --- a/dot_agents/skills/remix/references/middleware-and-server.md +++ b/dot_agents/skills/remix/references/middleware-and-server.md @@ -65,8 +65,9 @@ let router = createRouter({ middleware }) ### Static files vs browser modules -- Use `staticFiles()` for files that should be served directly from disk, such as images, fonts, or already-built assets in `public/` +- Use `staticFiles()` for files that should be served directly from disk, such as images, fonts, or already-built assets in the root `public/` directory - Use `remix/assets` when browser modules should be compiled and served from source files with import rewriting, preloads, or fingerprinted URLs +- `public/` directories inside `app/` hold browser-reachable source for the asset server ### Ordering notes @@ -118,11 +119,11 @@ Use `context.set(key, value)` to add typed values accessible downstream via `con ```typescript import type { Middleware } from 'remix/router' -import { Database } from 'remix/data-table' +import { databaseContext } from '~/middleware/database.ts' export function loadDatabase(): Middleware { return async (context, next) => { - context.set(Database, db) + context.set(databaseContext, db) return next() } } @@ -152,11 +153,11 @@ export function requireAdmin(): Middleware { // app/utils/context.ts import { getContext } from 'remix/middleware/async-context' import { Auth } from 'remix/middleware/auth' -import { Database } from 'remix/data-table' +import { databaseContext } from '~/middleware/database.ts' import { Session } from 'remix/session' export function getCurrentDb() { - return getContext().get(Database) + return getContext().get(databaseContext) } export function getCurrentSession() { diff --git a/dot_agents/skills/remix/references/mixins-styling-events.md b/dot_agents/skills/remix/references/mixins-styling-events.md index f708ce6..38fe1c1 100644 --- a/dot_agents/skills/remix/references/mixins-styling-events.md +++ b/dot_agents/skills/remix/references/mixins-styling-events.md @@ -115,6 +115,8 @@ Adds client-side navigation behavior to any element. Makes non-anchor elements b Options match `NavigationOptions`: `src`, `target`, `history` (`'push' | 'replace'`), `resetScroll`. +On a native anchor, the `history` option renders the corresponding `rmx-history="push|replace"` attribute so the enhanced navigation uses the same history behavior. + ## Native press and keyboard interactions Use native DOM events directly with `on(...)`. For buttons and links, `click` already includes keyboard activation when the element has the right semantics: diff --git a/dot_agents/skills/remix/references/routing-and-controllers.md b/dot_agents/skills/remix/references/routing-and-controllers.md index b7826dc..ba60e7f 100644 --- a/dot_agents/skills/remix/references/routing-and-controllers.md +++ b/dot_agents/skills/remix/references/routing-and-controllers.md @@ -103,7 +103,7 @@ export const search = createAction(routes.search, { The handler receives a context object with: -- `get(key)` — read a value set by middleware (e.g. `get(Database)`, `get(Session)`, `get(Auth)`) +- `get(key)` — read a value set by middleware (e.g. `get(databaseContext)`, `get(Session)`, `get(Auth)`) - `params` — typed route params - `url` — the request URL - `request` — the raw `Request` @@ -132,7 +132,7 @@ For pages, render a component tree and return the resulting `Response`: ```typescript async handler({ get }) { - let db = get(Database) + let db = get(databaseContext) let books = await db.findMany(books, { orderBy: ['id', 'asc'] }) return render(<IndexPage books={books} />) } @@ -152,7 +152,7 @@ async create({ get }) { return render(<NewBookPage errors={parsed.issues} />, { status: 400 }) } - let db = get(Database) + let db = get(databaseContext) let book = await db.create(books, parsed.value) return redirect(routes.books.show.href({ slug: book.slug }), 303) @@ -167,7 +167,7 @@ For expected failures — validation, conflict, not found — return a `Response ```typescript async show({ get, params }) { - let db = get(Database) + let db = get(databaseContext) let book = await db.find(books, params.bookId) if (!book) return new Response('Not Found', { status: 404 }) return render(<ShowPage book={book} />) @@ -208,7 +208,7 @@ If you find yourself returning JSON for what is really a browser form submission A controller owns the direct leaf routes in one route map. Each key in `actions` matches a direct leaf route key in the route definition passed to `router.map(...)`. Nested route-map keys do not belong inside a controller's `actions`; map those route maps with their own controllers. -Configure `RouterTypes.context` with your app context in the router module, then use `createController()` so `get(Database)`, `get(Session)`, `get(Auth)`, etc. are typed against your middleware stack without repeating a type clause on every controller. +Configure `RouterTypes.context` with your app context in the router module, then use `createController()` so `get(databaseContext)`, `get(Session)`, `get(Auth)`, etc. are typed against your middleware stack without repeating a type clause on every controller. ```typescript import { createController } from 'remix/router' @@ -218,13 +218,13 @@ import { routes } from '../routes.ts' export default createController(routes.books, { actions: { async index({ get }) { - let db = get(Database) + let db = get(databaseContext) let items = await db.findMany(books, { orderBy: ['id', 'asc'] }) return render(<IndexPage items={items} />) }, async show({ get, params }) { - let db = get(Database) + let db = get(databaseContext) let book = await db.find(books, params.bookId) if (!book) return new Response('Not Found', { status: 404 }) return render(<ShowPage book={book} />) @@ -359,4 +359,4 @@ declare module 'remix/router' { } ``` -This gives typed `context.get(Database)`, `context.get(Session)`, `context.get(Auth)`, etc. +This gives typed `context.get(databaseContext)`, `context.get(Session)`, `context.get(Auth)`, etc. diff --git a/dot_agents/skills/rust-testing/SKILL.md b/dot_agents/skills/rust-testing/SKILL.md index a2cab9c..464555b 100644 --- a/dot_agents/skills/rust-testing/SKILL.md +++ b/dot_agents/skills/rust-testing/SKILL.md @@ -1,6 +1,6 @@ --- name: rust-testing -description: Rust testing patterns including unit tests, integration tests, async testing, property-based testing, mocking, and coverage. Follows TDD methodology. +description: Rust testing patterns including unit tests, integration tests, async testing, property-based testing, mocking, and coverage. Follows TDD methodology. Use when writing Rust tests — unit, integration, async, property-based, or coverage. metadata: origin: ECC --- diff --git a/dot_bashrc b/dot_bashrc index 2ceeffb..8389601 100644 --- a/dot_bashrc +++ b/dot_bashrc @@ -25,13 +25,12 @@ fi # Flyline — enhanced Bash line editing (machine-optional, guarded) [ -f "$HOME/.local/lib/libflyline.so" ] && enable -f "$HOME/.local/lib/libflyline.so" flyline -# atuin shell history (needs bash-preexec) -[ -f "$HOME/.atuin/bin/env" ] && . "$HOME/.atuin/bin/env" -[ -f "$HOME/.bash-preexec.sh" ] && . "$HOME/.bash-preexec.sh" -command -v atuin >/dev/null 2>&1 && eval "$(atuin init bash)" # Generated for envman. Do not edit. [ -s "$HOME/.config/envman/load.sh" ] && . "$HOME/.config/envman/load.sh" # Prompt — starship, consistent with fish command -v starship >/dev/null 2>&1 && eval "$(starship init bash)" + +. "$HOME/.atuin/bin/env" +eval "$(atuin init bash)" diff --git a/dot_claude/settings.json b/dot_claude/settings.json index 4bab892..b1ca966 100644 --- a/dot_claude/settings.json +++ b/dot_claude/settings.json @@ -262,13 +262,13 @@ "matcher": ".*" }, { - "matcher": "tldraw-offline", "hooks": [ { - "type": "command", - "command": "sh '/home/prad/skills/tldraw-offline/inject-server-context.sh' SubagentStart" + "command": "sh '/home/prad/skills/tldraw-offline/inject-server-context.sh' SubagentStart", + "type": "command" } - ] + ], + "matcher": "tldraw-offline" } ], "SubagentStop": [ @@ -310,4 +310,4 @@ "skipAutoPermissionPrompt": true, "skipDangerousModePermissionPrompt": true, "tui": "fullscreen" -} +} \ No newline at end of file diff --git a/dot_config/fish/conf.d/00-path.fish b/dot_config/fish/conf.d/00-path.fish index 9016bf2..148bd33 100644 --- a/dot_config/fish/conf.d/00-path.fish +++ b/dot_config/fish/conf.d/00-path.fish @@ -7,7 +7,6 @@ fish_add_path $HOME/.grok/bin fish_add_path $HOME/.opencode/bin fish_add_path -a $HOME/.foundry/bin fish_add_path -a $HOME/local/share/pnpm -source $HOME/.atuin/bin/env.fish # pnpm set -gx PNPM_HOME $HOME/.local/share/pnpm diff --git a/dot_config/fish/conf.d/atuin.env.fish b/dot_config/fish/conf.d/atuin.env.fish deleted file mode 100644 index d058327..0000000 --- a/dot_config/fish/conf.d/atuin.env.fish +++ /dev/null @@ -1,2 +0,0 @@ - -source "$HOME/.atuin/bin/env.fish" diff --git a/dot_config/fish/config.fish b/dot_config/fish/config.fish index 1552191..b61fe65 100644 --- a/dot_config/fish/config.fish +++ b/dot_config/fish/config.fish @@ -30,10 +30,9 @@ status is-interactive; and test "$FISH_BOTTOM_PROMPT" = 1; and tput cup $LINES 0 fish_add_path $HOME/.grok/bin # <<< grok installer <<< +# Generated for envman. Do not edit. +test -s ~/.config/envman/load.fish; and source ~/.config/envman/load.fish if status is-interactive atuin init fish | source end - -# Generated for envman. Do not edit. -test -s ~/.config/envman/load.fish; and source ~/.config/envman/load.fish diff --git a/dot_config/fish/functions/fish_user_key_bindings.fish b/dot_config/fish/functions/fish_user_key_bindings.fish index 52aff71..1e2e40b 100644 --- a/dot_config/fish/functions/fish_user_key_bindings.fish +++ b/dot_config/fish/functions/fish_user_key_bindings.fish @@ -1,25 +1,33 @@ function fish_user_key_bindings - # Keys owned by kitty.conf never reach fish and are NOT bound here: - # ctrl-g → lazygit overlay, ctrl-a → omp hsplit, ctrl-y → yazi vsplit, - # ctrl-h/j/k/l → vim-aware split nav (pass_keys.py). - # ctrl-e: yazi, same as ctrl-f (cd's to yazi's exit dir on quit). - bind ctrl-e 'yy; commandline -f repaint' - bind ctrl-b purple repaint - bind ctrl-y clip-last + # Vi mode first: it rebinds the preset tables and (on first activation) + # re-triggers this function via the fish_key_bindings handler, so custom + # binds must come after it to land on top. + fish_vi_key_bindings - bind ctrl-o 'gho; commandline -f repaint' - bind ctrl-p 'pj; commandline -f repaint' - bind ctrl-a 'omp; commandline -f repaint' - # bind \ca 'launch_omp_livediff_overlay; commandline -f repaint' + # Vi mode types in `insert`; bind customs there AND in `default` (normal) + # so the keys work regardless of mode. + for mode in insert default + # Keys owned by kitty.conf never reach fish and are NOT bound here: + # ctrl-g → lazygit overlay, ctrl-a → omp hsplit, ctrl-y → yazi vsplit, + # ctrl-h/j/k/l → vim-aware split nav (pass_keys.py). + # ctrl-e: yazi, same as ctrl-f (cd's to yazi's exit dir on quit). + bind -M $mode ctrl-e 'yy; commandline -f repaint' + bind -M $mode ctrl-b purple repaint + bind -M $mode ctrl-y clip-last - # ctrl-f: pick file(s) with television and open in nvim. - bind ctrl-f 'yy; commandline -f repaint' - bind ctrl-g 'lazygit; commandline -f repaint' + bind -M $mode ctrl-o 'gho; commandline -f repaint' + bind -M $mode ctrl-p 'pj; commandline -f repaint' + bind -M $mode ctrl-a 'omp; commandline -f repaint' - # ctrl-c routes through the clear wrapper (kitty scrollback + prompt - # pinned to the bottom) AND wipes any text typed on the command line. - bind ctrl-c 'commandline -r ""; clear; commandline -f repaint' + # ctrl-f: pick file(s) with television and open in nvim. + bind -M $mode ctrl-f 'yy; commandline -f repaint' + bind -M $mode ctrl-g 'lazygit; commandline -f repaint' - # ctrl-q mirrors ctrl-d's preset (`exit`) — quit the shell. - bind ctrl-q exit + # ctrl-c routes through the clear wrapper (kitty scrollback + prompt + # pinned to the bottom) AND wipes any text typed on the command line. + bind -M $mode ctrl-c 'commandline -r ""; clear; commandline -f repaint' + + # ctrl-q mirrors ctrl-d's preset (`exit`) — quit the shell. + bind -M $mode ctrl-q exit + end end diff --git a/dot_config/kitty/kitty.conf b/dot_config/kitty/kitty.conf index 47ba216..2578cbf 100644 --- a/dot_config/kitty/kitty.conf +++ b/dot_config/kitty/kitty.conf @@ -46,8 +46,7 @@ map ctrl+shift+g launch_ov lazygit map ctrl+shift+f toggle_layout stack map ctrl+shift+e launch_tb nvim map ctrl+shift+a launch_vs omp -# suvadu history picker: pick → execute → drop to shell (script: ~/.local/bin/suv-pick) -map ctrl+shift+p launch_hs suv-pick +map ctrl+shift+p launch_hs atuin-pick.py # Misc map ctrl+shift+c copy_to_clipboard diff --git a/dot_config/niri/exact_config.d/30-window-rules.kdl b/dot_config/niri/exact_config.d/30-window-rules.kdl index cd629aa..c633b07 100644 --- a/dot_config/niri/exact_config.d/30-window-rules.kdl +++ b/dot_config/niri/exact_config.d/30-window-rules.kdl @@ -102,7 +102,7 @@ window-rule { match app-id="todoist" match app-id="com.anthropic.Claude" match app-id="Hermes" - + match app-id="chromium" open-on-output "eDP-1" } diff --git a/dot_config/niri/exact_config.d/91-internal-display.kdl b/dot_config/niri/exact_config.d/91-internal-display.kdl index ff2df3a..82d66bc 100644 --- a/dot_config/niri/exact_config.d/91-internal-display.kdl +++ b/dot_config/niri/exact_config.d/91-internal-display.kdl @@ -8,7 +8,7 @@ output "Tianma Microelectronics Ltd. TL134ADXP03 Unknown" { always-center-single-column true center-focused-column "never" gaps 8 - default-column-width { proportion 0.9; } + default-column-width { proportion 1.0; } preset-column-widths { proportion 0.5 proportion 1.0 diff --git a/dot_config/yazi/package.toml b/dot_config/yazi/package.toml index 7f8f6ab..0fac983 100644 --- a/dot_config/yazi/package.toml +++ b/dot_config/yazi/package.toml @@ -1,6 +1,6 @@ [[plugin.deps]] use = "yazi-rs/plugins:smart-filter" -rev = "0be29a9" +rev = "3f2b882" hash = "d6f7351ea25a43c9b5f4315fa1e23935" [[plugin.deps]] @@ -10,7 +10,7 @@ hash = "8c1b36103121a66b37308afaf7124575" [[plugin.deps]] use = "yazi-rs/plugins:smart-enter" -rev = "0be29a9" +rev = "3f2b882" hash = "187cc58ba7ac3befd49c342129e6f1b6" [[plugin.deps]] diff --git a/dot_local/bin/executable_atuin-pick.py b/dot_local/bin/executable_atuin-pick.py new file mode 100755 index 0000000..2b473d0 --- /dev/null +++ b/dot_local/bin/executable_atuin-pick.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""atuin-pick: atuin history picker for a kitty split (ctrl+shift+p). + +atuin search -i draws its TUI on the tty and prints the selected command +to stdout (Esc prints nothing). Filter to history from the current +directory. Run the selection with rc loaded so aliases and functions +resolve, then drop to an interactive shell. +""" +import os +import subprocess +import sys + +# kitty launches this outside a shell, so atuin's init never ran. +if "ATUIN_SESSION" not in os.environ: + os.environ["ATUIN_SESSION"] = subprocess.run( + ["atuin", "uuid"], stdout=subprocess.PIPE, text=True + ).stdout.strip() + +sel = subprocess.run( + ["atuin", "search", "-i", "--filter-mode", "directory"], + stdout=subprocess.PIPE, + text=True, +).stdout.strip() +if sel: + print(f"\u276f {sel}", file=sys.stderr) + subprocess.run(["zsh", "-ic", "--", sel]) +os.execvp("zsh", ["zsh", "-i"]) diff --git a/dot_local/bin/executable_suv-pick b/dot_local/bin/executable_suv-pick deleted file mode 100755 index 75809d7..0000000 --- a/dot_local/bin/executable_suv-pick +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/zsh -# suv-pick: suvadu history picker for a kitty split (ctrl+shift+p). -# suv search draws its TUI on stderr and prints the selected command to -# stdout (Esc prints nothing). Run the selection with rc loaded so aliases -# and functions resolve, then drop to an interactive shell. -sel="$(command suv search)" -if [[ -n "$sel" ]]; then - print -ru2 -- "❯ $sel" - zsh -ic -- "$sel" -fi -exec zsh -i diff --git a/dot_zshrc b/dot_zshrc index 8157660..ac01f07 100644 --- a/dot_zshrc +++ b/dot_zshrc @@ -29,8 +29,7 @@ fi # ZLE keybinds — zsh ports of fish_user_key_bindings.fish [ -f "$HOME/.config/zsh/keybinds.zsh" ] && . "$HOME/.config/zsh/keybinds.zsh" -# suvadu shell history (replaces atuin here; fish keeps atuin — suvadu has no -# fish support yet, bash still on atuin too) +# suvadu shell history command -v suv >/dev/null 2>&1 && eval "$(suv init zsh)" # Generated for envman. Do not edit. @@ -49,3 +48,7 @@ for _zsh_hl in \ [ -r "$_zsh_hl" ] && . "$_zsh_hl" && break done unset _zsh_hl + +. "$HOME/.atuin/bin/env" + +eval "$(atuin init zsh)"