본문으로 건너뛰기
cccskills
무료GitHub에 공개

pierre

Use Pierre Computer Company's `@pierre/trees` (path-first file tree) and `@pierre/diffs` (shiki-based code/diff renderer) in Kolu. Pierre ships Preact/vanilla cores with optional React wrappers — Kolu consumes the vanilla classes and wraps them in thin SolidJS components. Trigger when: wiring up a file tree, rendering unified diffs or syntax-highlighted files, replacing `@git-diff-view`, or any mention of `@pierre/trees` / `@pierre/diffs` / "pierre library".

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md12.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

@pierre/trees + @pierre/diffs integration

Kolu uses two Pierre packages for code-review surfaces (CodeTab):

  • @pierre/trees — virtualized, path-first file-tree UI with search, git status, drag-and-drop, context menus, icons, themes.
  • @pierre/diffs — shiki-backed unified/split diff renderer with annotations, line selection, virtualization, custom hunk separators. Since 1.2.x, the top-level CodeView class hosts one or more file/diff items in a single virtualized scroll viewport (advanced mode) and supersedes the per-file File / FileDiff / Virtualizer trio for kolu's consumer code.

Both publish a vanilla class API (Preact-rendered internally) plus optional React wrappers. Kolu consumes the vanilla core from SolidJS — no React.

  • Source for study: git clone https://github.com/pierrecomputer/pierre /tmp/pierre
  • npm: @pierre/trees@1.0.0-beta.4, @pierre/diffs@1.2.1

Why Pierre over hand-rolled

Pierre's libraries encapsulate three things Kolu used to own:

  1. Tree layout and virtualization (buildFileTree.ts, FileTree.tsx — removed). Pierre handles sort, collapse, sticky folders, keyboard nav, virtualization, search, and git status in one pass.
  2. Diff parsing and rendering (@git-diff-view/solid — removed). Pierre parses raw unified-diff strings with parsePatchFiles() and renders with syntax highlighting via Shiki.
  3. Theming. Pierre reads Shiki themes directly and exposes CSS variables for host-page overrides (--trees-*, --diffs-*).

SolidJS wrapping pattern

The vanilla classes own their DOM. SolidJS wrappers are thin — they only mount, update options reactively, and clean up. No re-render loop, no framework-internal state.

FileTree wrapper (shape)

// packages/solid-pierre/src/FileTree.tsx (sketch)
import { FileTree, type GitStatusEntry } from "@pierre/trees";
import { createEffect, onCleanup, on } from "solid-js";

export type FileTreeProps = {
  paths: string[]; // canonical repo-relative
  gitStatus?: GitStatusEntry[];
  selectedPath?: string | null;
  onSelect?: (path: string) => void;
};

export const FileTree: Component<FileTreeProps> = (props) => {
  let container!: HTMLDivElement;
  let tree: FileTreeClass | undefined;

  // Mount once — class owns its DOM. Don't recreate on prop changes.
  queueMicrotask(() => {
    tree = new FileTreeClass({
      paths: props.paths,
      initialExpansion: "open",
      search: true,
      gitStatus: props.gitStatus,
      onSelectionChange: (paths) => props.onSelect?.(paths[0] ?? ""),
    });
    tree.render({ containerWrapper: container });
  });

  // Reactively push updates via setters — `resetPaths`, `setGitStatus` patch
  // in place without rerenders.
  createEffect(
    on(
      () => props.paths,
      (paths) => tree?.resetPaths(paths),
      { defer: true },
    ),
  );
  createEffect(
    on(
      () => props.gitStatus,
      (g) => tree?.setGitStatus(g),
      { defer: true },
    ),
  );

  onCleanup(() => tree?.cleanUp());

  return <div ref={container!} class="h-full" />;
};

Key points:

  • Mount in queueMicrotask (or onMount) — the container div must be in the DOM before render().
  • Pass callbacks through props.onSelect?.() — don't capture at mount time, the prop ref may change. Pierre calls back through the current closure.
  • Use setters for updates (resetPaths, setGitStatus, setIcons, setComposition) — never reconstruct FileTree on prop change.
  • defer: true on the effects so the initial mount doesn't fire them.
  • onCleanup(() => tree?.cleanUp()) is mandatory — leaks the shadow root otherwise.

Git status mapping

Kolu's GitChangeStatus is a single letter (M / A / D / R / C / U / T / ?). Pierre's GitStatus is a word (modified, added, deleted, renamed, untracked, ignored). Map at the call site:

const MAP: Record<GitChangeStatus, GitStatus> = {
  M: "modified",
  A: "added",
  D: "deleted",
  R: "renamed",
  C: "renamed",
  U: "modified",
  T: "modified",
  "?": "untracked",
};

No hand-built tree needed. Pass the flat files.map(f => f.path) directly to paths and files.map(f => ({ path: f.path, status: MAP[f.status] })) to gitStatus. Pierre handles hierarchy, collapse-chains, and sort.

File-browser (lazy load) mode

@pierre/trees expects the full path list up front. For a lazy "browse the whole repo" mode, there are two options:

  1. Eagerly list all paths once — simpler, fine up to ~50k files. Use the server's fs.listDir recursively or add an fs.listAll endpoint.
  2. Synthesize paths on demand and call tree.add(path) / tree.batch([...]) as directories expand. Subscribe to the tree's expand events via the controller; requires deeper API reading.

The prototype replacement started with (1) — simpler, matches Pierre's path-first model.

CodeView wrapper (shape)

Since @pierre/diffs@1.2, the top-level CodeView class is the rendering substrate for both files and diffs. Kolu's @kolu/solid-pierre exposes a single <CodeView> Solid wrapper around it; the older per-class <FileView> / <FileDiff> / <Virtualizer> trio is retired.

// packages/solid-pierre/src/CodeView.tsx (sketch)
import {
  CodeView as CodeViewClass,
  type CodeViewItem,
  type CodeViewLineSelection,
  type CodeViewOptions,
  DEFAULT_THEMES,
} from "@pierre/diffs";

export type CodeViewProps = {
  items: readonly CodeViewItem[];
  theme: "light" | "dark";
  diffStyle?: "unified" | "split";
  overflow?: "scroll" | "wrap";
  enableLineSelection?: boolean;
  selectedLines?: CodeViewLineSelection | null;
  onSelectedLinesChange?: (selection: CodeViewLineSelection | null) => void;
  onError: (err: Error) => void;
  class?: string;
  style?: JSX.CSSProperties;
};

export const CodeView: Component<CodeViewProps> = (props) => {
  let root!: HTMLDivElement;
  let instance: CodeViewClass | undefined;

  const buildOptions = (): CodeViewOptions<undefined> => ({
    theme: DEFAULT_THEMES,
    themeType: props.theme,
    diffStyle: props.diffStyle ?? "unified",
    overflow: props.overflow ?? "wrap",
    enableLineSelection: props.enableLineSelection ?? false,
    onSelectedLinesChange: (s) => props.onSelectedLinesChange?.(s),
  });

  onMount(() => {
    instance = new CodeViewClass(buildOptions());
    instance.setup(root);  // root IS the scroll container
    instance.setItems(props.items);
  });

  createEffect(on(() => props.items, (items) => instance?.setItems(items), { defer: true }));
  createEffect(on(() => props.theme,  () => instance?.setOptions(buildOptions()), { defer: true }));
  onCleanup(() => instance?.cleanUp());

  return <div ref={root} class={props.class} style={props.style} />;
};

Key points:

  • The root <div> IS Pierre's scroll container. setup(root) registers it as the scroller. Put overflow-auto and sizing on it directly; do not nest another scroller inside.
  • items is typed. Pass { id, type: "file", file: { name, contents } } for a file or { id, type: "diff", fileDiff } for a diff. Get the FileDiffMetadata via parsePatchFiles(raw)[0]?.files[0] from the call site — parsePatchFiles is the top-level Pierre API for raw unified-diff strings.
  • Single-file case is just items={[oneItem]}. Pierre virtualizes unconditionally; there is no vanilla branch to opt into.
  • Selection is item-scoped. CodeViewLineSelection is { id, range } — the id must match one of the items currently in the viewport. When the rendered item changes (path swap), update selectedLines to either point at the new id or null.
  • Version tracking. Pierre's reconcileItems keeps the previous record when an item's version is unchanged — passing a fresh fileDiff/file for the same id without bumping version leaves stale content on screen. The wrapper diffs incoming items by reference and bumps version internally so callers don't deal with the field. If you build your own wrapper, replicate that bump.
  • Advanced mode bypasses legacy bugs. VirtualizedFile.setVisibility and VirtualizedFileDiff.setVisibility both early-return in advanced mode (isAdvancedMode() check), so the setVisibility upstream-bug workaround the old wrappers carried is no longer needed under CodeView.

Peer dependencies

Both packages declare react, react-dom as peer dependencies. They're only needed for the ./react entry points. The vanilla core bundles preact + preact-render-to-string as regular deps, so no React install is required when consuming @pierre/trees / @pierre/diffs directly from SolidJS.

Suppress peer-dep warnings in pnpm-workspace.yaml or .npmrc:

# pnpm-workspace.yaml
packageExtensions:
  "@pierre/trees@*":
    peerDependenciesMeta:
      react: { optional: true }
      react-dom: { optional: true }
  "@pierre/diffs@*":
    peerDependenciesMeta:
      react: { optional: true }
      react-dom: { optional: true }

Theming hookup

Trees reads CSS variables; expose kolu's theme tokens by setting them on the host element's inline style (or a wrapper class):

.pierre-trees-host {
  --trees-fg-override: theme(colors.fg);
  --trees-selected-bg-override: theme(colors.surface.2);
  --trees-border-color-override: theme(colors.edge);
}

For diffs, prefer DEFAULT_THEMES (pierre-dark/pierre-light) initially. Move to Kolu-branded Shiki themes later via registerCustomTheme() if needed.

What to port, what to keep

Removed in earlier passes (no longer in the tree):

  • packages/client/src/ui/FileTree.tsx
  • packages/client/src/ui/buildFileTree.ts
  • packages/client/src/ui/buildFileTree.test.ts
  • @git-diff-view/solid dep
  • highlight.js dep — the file browser's content viewer now runs through @pierre/diffs's CodeView (file item), same shiki pipeline.

Removed when CodeView landed:

  • packages/solid-pierre/src/Virtualizer.tsx — context-based dispatcher for vanilla vs. virtualized; advanced mode makes virtualization unconditional.
  • packages/solid-pierre/src/FileView.tsx — single-file viewer wrapper. The rAF + line-height-estimate scrollToLine fallback and the setVisibility upstream-bug patch retired with it.
  • packages/solid-pierre/src/FileDiff.tsx — single-diff viewer wrapper.

Keep (not replaced by pierre):

  • packages/common/src/contract.ts — GitDiffOutputSchema still carries the raw unified diff. Consumers now parsePatchFiles() it instead of handing parsed hunks to @git-diff-view.
  • Sub-tab state (local / branch / browse) in useRightPanel — pierre doesn't know about Kolu's diff modes.

Gotchas

  1. Shadow DOM: both libs render into a shadow root for CSS isolation. Tailwind classes on children won't pierce in. Style via CSS variables or unsafeCSS option, not Tailwind utilities inside the tree rows. CodeView creates one <diffs-container> shadow root per item — selection-walkers must descend recursively (see packages/client/src/comments/shadowWalk.ts).
  2. Path-first identity: pierre's public API is keyed on path strings. Do not store or compare internal numeric IDs. CodeViewItem.id is also path-keyed by convention.
  3. Async load: pierre's diff renderer loads shiki WASM lazily. First render of a new language is async; the render() call returns immediately and the rows paint in a later frame. Do not race cleanup.
  4. setGitStatus([]) clears statuses — pass undefined to leave alone.
  5. resetPaths discards expansion state unless you pass initialExpandedPaths in the reset options.
  6. setItems reconciles by id; same-id same-version is treated as no-change. Bump version when content swaps for an existing id.
  7. Nix fetchPnpmDeps hash must be regenerated after adding new deps; see the nix-typescript skill.

Development tips

  • Pierre's benchmarks live in /tmp/pierre/packages/trees/scripts/ and /tmp/pierre/packages/diffs/scripts/ — useful to understand expected usage at scale.
  • Pierre's own demo: cd /tmp/pierre && bun install && bun run demo:dev (but their demo is Preact-based; reference only).
  • React wrappers in /tmp/pierre/packages/diffs/src/react/ are the clearest reference for "what the intended consumer does" — especially react/CodeView.tsx, which mirrors the SolidJS wrapper's prop shape.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

blog-post

무료

Write a kolu blog post grounded in the real build history — mine the Claude Code session logs behind a feature for the actual story (for large efforts, fan out over the transcripts with an ultracode workflow), draft it in the author's voice, and wire it into the Astro site. Use when asked to write a blog post or engineering essay about something that was built, especially one that should read as a narrative of what actually happened rather than invented marketing copy.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

juspay/kolu822026년 10월 19일 업데이트

ci

무료

Run this repo's CI end-to-end — the kolu-specific procedure on top of the odu runner: the odu MCP front door, banned opt-out flags, linux-only platform pinning, venue-pool leasing, fail-fast surfacing, e2e timing evidence, and the green-gate. Triggers on "run CI", "drive CI", "re-run the pipeline", "close the red check", "warm the pool". Runner mechanics live in the `/odu` skill.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

juspay/kolu822026년 10월 19일 업데이트

Launch the kolu dev server on two RANDOM free ports so it never collides with the running production `kolu.service`, remember the ports for the rest of the session, and tear down only the dev instance. Load before running the app locally — for evidence capture, driving a live kolu with the chrome-devtools MCP, or any `just dev` you'd otherwise run by hand. Triggers on "run kolu locally", "launch the dev server", "boot kolu", "drive a live kolu", "start the app to screenshot it", or before pointing chrome-devtools at a local kolu.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

juspay/kolu822026년 10월 19일 업데이트

diataxis

무료

Classify, write, and audit documentation per Diátaxis (diataxis.fr) — the four-quadrant method (tutorials · how-to guides · reference · explanation). Encodes the compass as a decision procedure, each quadrant's contract as hard rules, and an executable mixed-mode audit. Project-agnostic; the per-repo doc-location mapping lives in a repo rule of the project's own (kolu: `.apm/instructions/diataxis.instructions.md`). Use when writing any user-facing doc, structuring a docs site, or auditing existing docs for mixed modes.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

juspay/kolu822026년 10월 19일 업데이트

evidence

무료

Produce visual PR evidence — a screenshot or video — whenever a change has on-screen impact. Tests are never a substitute; a change can be backend by cause and visible by effect. Capture via the project's e2e harness on a pu box, or by driving a live kolu with the chrome-devtools MCP; host on a GitHub release and post a `## Evidence` comment. Triggers on "post evidence", "screenshot the change", "record a video of this", "show it working", "prove it", or finishing any change whose effect is visible on screen.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

juspay/kolu822026년 10월 19일 업데이트

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

juspay/kolu822026년 10월 19일 업데이트

juspay의 스킬 모두 보기

이 스킬의 문제 신고