mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 11:32:42 +00:00
chs: re-add drifted files
dot_agents/dot_skill-lock.json
This commit is contained in:
1 parent
03ef5c94ea
commit
42d17e825e
30 files changed
+199
-114
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
@@ -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/<slug>`**, 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.
|
||||
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ The codemod touches **only frontmatter**. Icons in MDX **body** (`<Icon icon="
|
||||
|
||||
Mintlify's `navigation` object (`tabs`/`anchors`/`dropdowns`/`products`/`versions`/`languages`/`groups`/`pages`) is fully config-declared. **Prefer restructuring content into folders**, not porting the config verbatim:
|
||||
|
||||
- **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page — is **not** a `meta.ts` field; it's the global `navigation.sidebar.display` in `blume.config.ts`.)
|
||||
- **`groups`** (`{ group, pages: [...] }`) → a folder per group. The `group` name → the folder's humanized name or a `meta.ts` `title`. Nested groups → nested folders. `expanded: false` → `meta.ts` `collapsed: true` (inverted). `tag` → the folder/page `sidebar.badge`. **Folder metadata is a `meta.ts` module** (`export default defineMeta({ title, icon, order, collapsed, pages, display })`), a TypeScript file — **not** JSON. Blume does not read a `meta.json`, so any folder-level config you carry over from the source (or hand-write to preserve a group's label/order/collapse) must be authored as `meta.ts`. (Render mode — flat/group/page — defaults to the global `navigation.sidebar.display` in `blume.config.ts`; a folder overrides its own group with `meta.ts` `display`, or `sidebar.display` in its `index` page's frontmatter.)
|
||||
- **Config-only nested groups don't exist on disk — you must materialize them, or they flatten silently.** A nested `{ group, pages }` almost never has a matching subfolder: its pages sit **flat in the parent directory** (e.g. `platform/analytics/getting-started.mdx`, `…/quick-reference.mdx`) and the grouping lives **only** in the `docs.json` `pages` array. If you leave the files where they are, filesystem-derived nav sees one flat folder and the inner group vanishes — Mintlify's `Analytics → Reference → {…}` becomes a flat `Analytics → {…}`. To preserve it you must **either** move those pages into a real subfolder (`platform/analytics/reference/`, with a `meta.ts` for the label/`collapsed`), **or** declare the shape in an explicit `navigation.sidebar`. Walk **every** `pages` array recursively and treat any nested `group` object as a folder-move to plan, not files already in place. When inventorying the nav (workflow step 2), record the config nesting depth separately from the on-disk depth — they diverge exactly here.
|
||||
- **This is the canonical case for `navigation.sidebar` over folders.** Materializing a purely-presentational nested group as a subfolder changes URLs (`/platform/analytics/getting-started` → `/platform/analytics/reference/getting-started`) for no reason other than a visual grouping, forcing a `redirects` entry per page. When you want to keep the nesting **and** the URLs, an explicit `navigation.sidebar` group (`{ label, items }`) is the better trade — it nests the existing routes without moving any file. Pick per group; don't reflexively flatten.
|
||||
- **`pages`** entries are page refs (paths without extension) → files at the corresponding path. An entry that's `"GET /path"` is an OpenAPI endpoint stub → **delete it** (Blume generates these; see OpenAPI).
|
||||
|
||||
@@ -44,3 +44,4 @@ Include these sections in the PR body or no-op summary:
|
||||
- Verification run (commands and results)
|
||||
- Skipped checks, with reasons
|
||||
- Remaining risk or follow-up
|
||||
# chs-e2e-test
|
||||
@@ -176,7 +176,14 @@ Validate spec structure and auto-fix common issues:
|
||||
import { validateSpec, autoFixSpec } from "@json-render/core";
|
||||
|
||||
const { valid, issues } = validateSpec(spec);
|
||||
const fixed = autoFixSpec(spec);
|
||||
// issues include: missing_child, invalid_visible (malformed conditions),
|
||||
// repeat_without_children, repeat_state_mismatch (statePath not an array in state)
|
||||
|
||||
const { spec: fixed, fixDetails } = autoFixSpec(spec);
|
||||
// fixDetails entries are { message, lossy }. Lossless fixes relocate
|
||||
// misplaced fields; lossy fixes prune dangling children references.
|
||||
// In a repair loop, withhold lossy fixes until retries are exhausted:
|
||||
const attempt = autoFixSpec(spec, { lossy: retriesExhausted });
|
||||
```
|
||||
|
||||
## Visibility Conditions
|
||||
@@ -253,7 +260,7 @@ The `StateStore` interface: `get(path)`, `set(path, value)`, `update(updates)`,
|
||||
| `diffToPatches` | Generate RFC 6902 JSON Patch operations from object diff |
|
||||
| `EditMode` | Type: `"patch" \| "merge" \| "diff"` |
|
||||
| `validateSpec` | Validate spec structure |
|
||||
| `autoFixSpec` | Auto-fix common spec issues |
|
||||
| `autoFixSpec` | Auto-fix common spec issues; classifies fixes lossy/lossless, `{ lossy: false }` withholds pruning |
|
||||
| `createSpecStreamCompiler` | Stream JSONL patches into spec |
|
||||
| `createJsonRenderTransform` | TransformStream separating text from JSONL in mixed streams |
|
||||
| `parseSpecStreamLine` | Parse single JSONL line |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: domain-modeling
|
||||
description: Build and sharpen a project's domain model. Use when the user wants to pin down domain terminology or a ubiquitous language, record an architectural decision, or when another skill needs to maintain the domain model.
|
||||
description: Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a CONTEXT.md, or recording or editing an ADR.
|
||||
---
|
||||
|
||||
# Domain Modeling
|
||||
|
||||
@@ -47,6 +47,11 @@ Options:
|
||||
-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
|
||||
```
|
||||
@@ -87,8 +92,13 @@ Options:
|
||||
|
||||
-h, --help - Show this help.
|
||||
--workspace <slug> - Target workspace (uses credentials)
|
||||
--project <project> - Filter by project (slug or name)
|
||||
--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)
|
||||
```
|
||||
@@ -112,7 +122,13 @@ Options:
|
||||
-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)
|
||||
--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
|
||||
```
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
---
|
||||
|
||||
+3
-4
@@ -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)"
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
source "$HOME/.atuin/bin/env.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
|
||||
@@ -1,25 +1,33 @@
|
||||
function fish_user_key_bindings
|
||||
# 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
|
||||
|
||||
# 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 ctrl-e 'yy; commandline -f repaint'
|
||||
bind ctrl-b purple repaint
|
||||
bind ctrl-y clip-last
|
||||
bind -M $mode ctrl-e 'yy; commandline -f repaint'
|
||||
bind -M $mode ctrl-b purple repaint
|
||||
bind -M $mode ctrl-y clip-last
|
||||
|
||||
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'
|
||||
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-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-f 'yy; commandline -f repaint'
|
||||
bind -M $mode ctrl-g 'lazygit; 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'
|
||||
bind -M $mode ctrl-c 'commandline -r ""; clear; commandline -f repaint'
|
||||
|
||||
# ctrl-q mirrors ctrl-d's preset (`exit`) — quit the shell.
|
||||
bind ctrl-q exit
|
||||
bind -M $mode ctrl-q exit
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]]
|
||||
|
||||
Executable
+27
@@ -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"])
|
||||
@@ -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
|
||||
@@ -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)"
|
||||
Reference in new issue
Block a user