context-attachment-manager
Codex and Claude Code plugin (works in Claude Desktop): choose which past images go to the model with your next message. Unchecked images become placeholders, the conversation goes on, and requests st
翻译准备中
制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗A TanStack DevTools plugin for inspecting LogTape logs in the browser.
Filter, search, and inspect structured logs in real time — without leaving the browser DevTools overlay.
pnpm add @mugenlabs/logtape-devtools @logtape/logtape @tanstack/react-devtools
createLogTapeDevtools() returns a sink and a panel plugin already wired to the same store. This is the recommended path.
import { configure, getLogger } from "@logtape/logtape";
import { createLogTapeDevtools } from "@mugenlabs/logtape-devtools";
import { TanStackDevtools } from "@tanstack/react-devtools";
const { sink, plugin } = createLogTapeDevtools();
await configure({
sinks: { devtools: sink },
loggers: [{ category: [], lowestLevel: "trace", sinks: ["devtools"] }],
});
getLogger(["app", "auth"]).info("User {username} logged in", { username: "john_doe" });
function App() {
return (
<>
<YourApp />
<TanStackDevtools plugins={[plugin]} />
</>
);
}
Use the individual factories when you need to hold on to the store — for example to clear it from a test, to run several isolated stores, or to configure LogTape in a file that must not import React.
// logging.ts
import { configure } from "@logtape/logtape";
import { createDevtoolsSink, createLogStore } from "@mugenlabs/logtape-devtools";
export const logStore = createLogStore({ maxRecords: 5000 });
await configure({
sinks: { devtools: createDevtoolsSink({ store: logStore }) },
loggers: [{ category: ["app"], lowestLevel: "debug", sinks: ["devtools"] }],
});
// app.tsx
import { createLogTapeDevtoolsPlugin } from "@mugenlabs/logtape-devtools";
import { TanStackDevtools } from "@tanstack/react-devtools";
import { logStore } from "./logging";
const plugin = createLogTapeDevtoolsPlugin({ store: logStore });
export function App() {
return <TanStackDevtools plugins={[plugin]} />;
}
The sink and the plugin must be given the same store instance, otherwise the panel stays empty.
@mugenlabs/logtape-devtools/sinkThe package root pulls in React (the panel is a React component). If your LogTape configuration is shared with a server entry point, a worker, or any non-React bundle, import from @mugenlabs/logtape-devtools/sink instead — it exports the sink, the store, and the types with no React dependency.
import { createDevtoolsSink, createLogStore } from "@mugenlabs/logtape-devtools/sink";
Exported from /sink: createDevtoolsSink, createLogStore, defaultLogStore, LOG_LEVELS, and the types DevtoolsSink, DevtoolsSinkOptions, LogStore, LogStoreOptions, DevtoolsLogRecord, LogLevel.
With React Server Components (Next.js App Router), the package root is marked "use client", so a Server Component can render the panel. Anything server code imports from the root is a client reference, not a function it can call, so configure LogTape on the server from /sink.
The panel is a development tool. Guard the mount so bundlers can tree-shake it — and @tanstack/react-devtools along with it — out of production builds:
import { lazy, Suspense } from "react";
const Devtools = import.meta.env.DEV
? lazy(() => import("./devtools"))
: () => null;
export function App() {
return (
<>
<YourApp />
<Suspense fallback={null}>
<Devtools />
</Suspense>
</>
);
}
// devtools.tsx — only ever imported in development
import { createLogTapeDevtoolsPlugin } from "@mugenlabs/logtape-devtools";
import { TanStackDevtools } from "@tanstack/react-devtools";
import { logStore } from "./logging";
// Create the plugin once, outside the component, so it is not re-created on every render.
const plugin = createLogTapeDevtoolsPlugin({ store: logStore });
export default function Devtools() {
return <TanStackDevtools plugins={[plugin]} />;
}
With webpack or other bundlers, process.env.NODE_ENV !== "production" works the same way — the important part is that the import() sits behind a statically analyzable flag.
Two related behaviours make the sink itself cheap to leave in place:
process.env.NODE_ENV === "production", captureStackTrace is ignored (minified bundles produce meaningless file and line references, and browsers do not apply source maps to Error.stack). Set forceStackTrace: true to override.store.hasListeners() on every record and does no stack work while nothing is subscribed. Message text is rendered lazily, so an unmounted panel costs one size-limited copy per record.Records are still buffered in memory when no panel is mounted. If you do not want that in production either, keep the sink out of your production LogTape configuration, or pass createLogStore({ maxRecords: 0 }) — the sink then returns before doing any work at all.
Records are copied when they are logged, within size limits. The sink copies each record's properties and interpolated message values, so changing an object after logging it does not change what the panel shows, and the store holds no references into your app. The copy keeps Date, Map, Set, RegExp and Error values, including an error's custom fields such as code. Objects with a toJSON method are stored as what toJSON returns. Functions, DOM nodes and binary data become short descriptions such as [HTMLButtonElement]. To bound memory, these are replaced with [Truncated: …] markers:
Map or SetcreateLogTapeDevtools(options?)Creates a LogTape sink and its matching DevTools plugin, both wired to the same store. Returns { sink, plugin }.
| Option | Type | Default | Description |
|---|---|---|---|
sink | Omit<DevtoolsSinkOptions, "store"> | {} | Sink options; the shared store is wired automatically |
plugin | Omit<LogTapeDevtoolsPluginOptions, "store"> | {} | Plugin options; the shared store is wired automatically |
store | LogStore | defaultLogStore | Store shared by the sink and the plugin |
const { sink, plugin } = createLogTapeDevtools({
sink: { captureStackTrace: true },
plugin: { defaultOpen: false, name: "Logs" },
});
createDevtoolsSink(options?)Creates a LogTape sink that forwards log records to the devtools store. Returns a DevtoolsSink — a LogTape Sink that is also Disposable, so LogTape can dispose it when its configuration is reset (a disposed sink stops writing).
| Option | Type | Default | Description |
|---|---|---|---|
captureStackTrace | boolean | true | Capture the source location (file:line:col) of each log call by parsing new Error().stack. Experimental and engine-dependent. Ignored in production builds and skipped while no panel is subscribed, unless forceStackTrace is set |
forceStackTrace | boolean | false | Capture stack traces even in production builds and while the panel is closed. Only has an effect when captureStackTrace is true |
store | LogStore | defaultLogStore | Store to write records into. Must match the store given to the plugin |
createLogTapeDevtoolsPlugin(options?)Creates a TanStack DevTools plugin (TanStackDevtoolsReactPlugin from @tanstack/react-devtools) to pass to <TanStackDevtools plugins={[...]} />. Create it once at module scope rather than inside a component.
| Option | Type | Default | Description |
|---|---|---|---|
defaultOpen | boolean | true | Whether the LogTape panel starts expanded |
name | string | "LogTape" | Display name shown in the DevTools tab bar |
store | LogStore | defaultLogStore | Store to read records from. Must match the store given to the sink |
createLogStore(options?)Creates an isolated log store. Records live in a fixed-capacity ring buffer, so appending is O(1) and the oldest record is evicted once the buffer is full. Listener notifications are coalesced into a microtask, so a synchronous burst of log calls triggers a single re-render.
| Option | Type | Default | Description |
|---|---|---|---|
maxRecords | number | 1000 | Maximum number of records to retain. 0 disables buffering entirely |
The returned LogStore exposes:
| Member | Signature | Description |
|---|---|---|
addRecord | (record: DevtoolsLogRecord) => void | Appends a record, evicting the oldest when full |
clear | () => void | Removes every record and notifies subscribers |
getSnapshot | () => DevtoolsLogRecord[] | Current records, oldest first. Reference is stable between mutations (safe for useSyncExternalStore) |
getMaxRecords | () => number | Current retention limit. Optional for custom stores; when it returns 0 the sink skips all work |
hasListeners | () => boolean | Whether anything is currently subscribed |
setMaxSize | (size: number) => void | Updates the retention limit, trimming if needed |
subscribe | (listener: () => void) => () => void | Registers a change listener; returns an unsubscribe function |
Breaking change in 0.2: custom
LogStoreimplementations must now providehasListeners().
defaultLogStoreThe shared LogStore instance (created with the default maxRecords: 1000) used whenever no explicit store is passed.
DevtoolsLogRecord — a LogTape record normalized for display:
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier, stable for the record's lifetime |
timestamp | number | Creation time in ms since the Unix epoch |
level | LogLevel | Severity of the record |
category | string[] | Logger category path, e.g. ["app", "auth"] |
categoryKey | string | undefined | Precomputed category.join(".") for filter/search (set by the sink) |
message | unknown[] | Message parts, alternating literals and interpolated values. Values are copied at log time, within size limits |
messageText | string | Message parts rendered into one searchable string (computed lazily on first access) |
messageSearchText | string | undefined | Cached lowercase messageText for search (lazy; set by the sink) |
properties | Record<string, unknown> | Structured properties, copied at log time within size limits |
caller | string | undefined | Source location, present only when stack capture ran |
LogLevel — "trace" | "debug" | "info" | "warning" | "error" | "fatal", also available as the ordered array LOG_LEVELS.
No logs appear in the panel.
sinks is not enough — every logger you care about needs it in its own sinks array. { category: [], lowestLevel: "trace", sinks: ["devtools"] } catches everything.lowestLevel. LogTape filters below it before the sink ever runs, so lowestLevel: "info" means logger.debug(...) never reaches the panel.createDevtoolsSink and createLogTapeDevtoolsPlugin. Passing it to only one of them leaves the panel reading an empty store. createLogTapeDevtools() avoids this by wiring both for you.defaultLogStore is per module instance.Source locations (caller) are missing.
forceStackTrace: true if your build is unminified and you want it anyway.forceStackTrace: true.Error.stack format. Frames from @logtape/* packages and from this package are skipped, matched on the file path. If you log through your own wrapper module, the location shown is the line inside the wrapper.Several sinks writing into one store.
maxRecords is a budget for the store as a whole.| Peer dependency | Range | Notes |
|---|---|---|
@logtape/logtape | ^2.0.0 | Optional — only types are imported; you need it to configure LogTape anyway |
react | ^18.0.0 || ^19.0.0 | Required for the panel |
react-dom | ^18.0.0 || ^19.0.0 | Required for the panel |
@tanstack/react-devtools | ^0.9.0 | Optional — only needed to host the panel. Bound to 0.9.x because 0.10 changed how props are passed into plugins |
Node >=20.0.0. The package is published as ESM. Node 20.19+ and 22.12+ can also require() it, and tools that do not set the import condition resolve it through default. @tanstack/react-devtools being optional means you can depend on this package purely for the sink (via @mugenlabs/logtape-devtools/sink) without installing the DevTools shell.
See the live demo and full documentation at logtape-devtools.mugenlabs.dev.
Requires Node 24 LTS (see .nvmrc) and pnpm >=10.
pnpm install
pnpm build # build the package
pnpm dev # rebuild the package on change
pnpm dev:demo # run the demo site on http://localhost:3001
Common checks:
pnpm typecheck # build + tsgo across package and demo
pnpm test:ci # unit tests (Vitest)
pnpm test-storybook # component tests via Storybook + Vitest browser mode
pnpm test:e2e # Playwright end-to-end tests
pnpm lint:fix # Biome check --write
This README is also the npm README: pnpm readme:sync copies it to packages/core/README.md, without this section.
| Path | Contents |
|---|---|
packages/core | The published package — sink, store, and the React panel, plus its Storybook stories |
apps/demo | The demo and documentation site (Vite + TanStack Router) deployed at logtape-devtools.mugenlabs.dev |
e2e | Playwright specs driving the demo app against a real DevTools panel |
scripts | CI and repo scripts (path filter, audits, peer pinning, README sync, OG image) |
See CONTRIBUTING.md for the full workflow, including the Changesets release process.