# Artifacts Feature Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add an `ArtifactTool` (deferred) that uploads local HTML to `cloud-artifacts` service and returns a public URL + hash, a `/artifacts` panel command to browse uploaded files in the current session, and a `/use-artifacts` bundled skill that teaches the agent when/how to use artifacts. **Architecture:** Tool is a deferred `@claude-code-best/builtin-tools` entry that wraps a `fetch`-based HTTP client; the client reads token/URL from hardcoded defaults with env var override, parses the `{error}` field in body for failure detection (Deno Deploy proxy flattens HTTP status to 200). The panel command is a `local-jsx` slash command that scans `context.messages` for `artifact` tool_use + tool_result pairs. The skill is a bundled skill that injects guidance on artifact types, cadence, and the two-step `SearchExtraTools` + `ExecuteExtraTool` invocation flow. **Tech Stack:** Bun, TypeScript strict, Zod v4 (`zod/v4`), React (Ink via `packages/@ant/ink`), `bun:test`. --- ## File Structure | File | Responsibility | |------|----------------| | `packages/builtin-tools/src/tools/ArtifactTool/config.ts` | Token/URL constants + env var override helpers | | `packages/builtin-tools/src/tools/ArtifactTool/client.ts` | `uploadArtifact()` — HTTP POST to cloud-artifacts, body error parsing | | `packages/builtin-tools/src/tools/ArtifactTool/prompt.ts` | `ARTIFACT_TOOL_NAME`, async `description()` / `prompt()` | | `packages/builtin-tools/src/tools/ArtifactTool/ArtifactTool.ts` | `buildTool()` definition: schema, `call`, render, map | | `packages/builtin-tools/src/tools/ArtifactTool/__tests__/client.test.ts` | client unit tests (mock fetch) | | `packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts` | Tool end-to-end tests (real temp file + mock fetch) | | `packages/builtin-tools/src/index.ts` | Barrel export for `ArtifactTool` (modify) | | `src/tools.ts` | Register `ArtifactTool` in the tools array (modify) | | `src/skills/bundled/useArtifacts.ts` | Bundled skill body + `registerUseArtifactsSkill()` | | `src/skills/bundled/index.ts` | Call `registerUseArtifactsSkill()` in `initBundledSkills()` (modify) | | `src/commands/artifacts/scanner.ts` | Pure `extractArtifacts(messages)` — scan tool_use/tool_result pairs | | `src/commands/artifacts/__tests__/scanner.test.ts` | scanner unit tests | | `src/commands/artifacts/ArtifactsMenu.tsx` | React/Ink list component with Enter/c/Esc | | `src/commands/artifacts/artifacts.tsx` | `call(onDone, context)` entry — calls scanner, renders menu | | `src/commands/artifacts/index.ts` | `satisfies Command` definition with lazy `load` | | `src/commands.ts` | Register `artifacts` command (modify) | --- ## Task 1: ArtifactTool config (token/URL defaults) **Files:** - Create: `packages/builtin-tools/src/tools/ArtifactTool/config.ts` - [ ] **Step 1: Write config file** ```typescript // packages/builtin-tools/src/tools/ArtifactTool/config.ts /** * Cloud Artifacts service configuration. * Token/URL have hardcoded production defaults; env vars override for self-hosted deployments. */ export const ARTIFACTS_DEFAULT_TOKEN = 'claude-code-best' export const ARTIFACTS_DEFAULT_URL = 'https://cloud-artifacts.claude-code-best.win' export function getArtifactsToken(): string { return process.env.CLAUDE_ARTIFACTS_TOKEN ?? ARTIFACTS_DEFAULT_TOKEN } export function getArtifactsBaseUrl(): string { return process.env.CLAUDE_ARTIFACTS_URL ?? ARTIFACTS_DEFAULT_URL } /** Strip trailing slash so `${base}/upload` is well-formed. */ export function getUploadUrl(): string { const base = getArtifactsBaseUrl() return base.endsWith('/') ? `${base}upload` : `${base}/upload` } ``` - [ ] **Step 2: Commit** ```bash git add packages/builtin-tools/src/tools/ArtifactTool/config.ts git commit -m "feat(artifact): add cloud-artifacts config with token/URL defaults" ``` --- ## Task 2: ArtifactTool client (TDD — uploadArtifact) **Files:** - Create: `packages/builtin-tools/src/tools/ArtifactTool/client.ts` - Test: `packages/builtin-tools/src/tools/ArtifactTool/__tests__/client.test.ts` - [ ] **Step 1: Write the failing test** ```typescript // packages/builtin-tools/src/tools/ArtifactTool/__tests__/client.test.ts import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { uploadArtifact } from '../client.js' const originalFetch = globalThis.fetch function mockFetch(body: object, status = 200): typeof fetch { return mock((_url: string | URL | Request, _init?: RequestInit) => Promise.resolve( new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' }, }), ), ) as unknown as typeof fetch } describe('uploadArtifact', () => { afterEach(() => { globalThis.fetch = originalFetch }) test('returns id/url/expiresAt on successful upload', async () => { globalThis.fetch = mockFetch({ id: 'V1StGXR8_Z5jdHi6B', url: 'https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B.html', expiresAt: '2026-06-27T10:00:00.000Z', }) const result = await uploadArtifact({ html: '

hello

', token: 'test-token', uploadUrl: 'https://example.test/upload', }) expect(result).toEqual({ id: 'V1StGXR8_Z5jdHi6B', url: 'https://cloud-artifacts.claude-code-best.win/7d/V1StGXR8_Z5jdHi6B.html', expiresAt: '2026-06-27T10:00:00.000Z', }) }) test('passes hash as query param when provided', async () => { const fetchMock = mockFetch({ id: 'my-id', url: 'https://x/y.html', expiresAt: '2026-06-27T00:00:00.000Z' }) globalThis.fetch = fetchMock await uploadArtifact({ html: '

x

', token: 't', uploadUrl: 'https://example.test/upload', hash: 'my-id', }) const calledUrl = (fetchMock as unknown as { mock: { calls: [string | URL | Request][] } }).mock.calls[0][0] expect(calledUrl.toString()).toContain('hash=my-id') }) test('passes ttl=30 query param when provided', async () => { const fetchMock = mockFetch({ id: 'x', url: 'https://x', expiresAt: '2026-07-20T00:00:00.000Z' }) globalThis.fetch = fetchMock await uploadArtifact({ html: '

x

', token: 't', uploadUrl: 'https://example.test/upload', ttl: 30, }) const calledUrl = (fetchMock as unknown as { mock: { calls: [string | URL | Request][] } }).mock.calls[0][0] expect(calledUrl.toString()).toContain('ttl=30') }) test('throws with error code when body contains {error} (Deno Deploy flattens status)', async () => { globalThis.fetch = mockFetch({ error: 'payload_too_large' }, 200) await expect( uploadArtifact({ html: 'x'.repeat(100), token: 't', uploadUrl: 'https://example.test/upload', }), ).rejects.toThrow(/payload_too_large/) }) test('throws on non-JSON body', async () => { globalThis.fetch = mock((_u: string | URL | Request) => Promise.resolve(new Response('Internal Server Error', { status: 500 })), ) as unknown as typeof fetch await expect( uploadArtifact({ html: '

', token: 't', uploadUrl: 'https://example.test/upload' }), ).rejects.toThrow() }) }) ``` - [ ] **Step 2: Run test to verify it fails** ```bash bun test packages/builtin-tools/src/tools/ArtifactTool/__tests__/client.test.ts ``` Expected: FAIL with `Cannot find module '../client.js'` or similar. - [ ] **Step 3: Implement the client** ```typescript // packages/builtin-tools/src/tools/ArtifactTool/client.ts export type UploadResult = { id: string url: string expiresAt: string } export type UploadParams = { html: string token: string uploadUrl: string hash?: string ttl?: 7 | 30 } export async function uploadArtifact(params: UploadParams): Promise { const url = new URL(params.uploadUrl) if (params.hash) url.searchParams.set('hash', params.hash) if (params.ttl) url.searchParams.set('ttl', String(params.ttl)) const response = await fetch(url, { method: 'POST', headers: { Authorization: `Bearer ${params.token}`, 'Content-Type': 'text/html', }, body: params.html, }) // Deno Deploy proxy flattens upstream status to 200; the Worker embeds the // real error in the body as `{ "error": "" }`. Always parse body first. const text = await response.text() let parsed: unknown try { parsed = JSON.parse(text) } catch { throw new Error(`Artifact upload failed: HTTP ${response.status} (non-JSON body)`) } if (parsed && typeof parsed === 'object' && 'error' in parsed) { const code = (parsed as { error: unknown }).error throw new Error(`Artifact upload failed: ${String(code)}`) } const data = parsed as Partial if (typeof data.id !== 'string' || typeof data.url !== 'string' || typeof data.expiresAt !== 'string') { throw new Error(`Artifact upload returned malformed body: ${text.slice(0, 200)}`) } return { id: data.id, url: data.url, expiresAt: data.expiresAt } } ``` - [ ] **Step 4: Run tests to verify they pass** ```bash bun test packages/builtin-tools/src/tools/ArtifactTool/__tests__/client.test.ts ``` Expected: PASS (5 tests). - [ ] **Step 5: Commit** ```bash git add packages/builtin-tools/src/tools/ArtifactTool/client.ts packages/builtin-tools/src/tools/ArtifactTool/__tests__/client.test.ts git commit -m "feat(artifact): add HTTP client with body-error parsing" ``` --- ## Task 3: ArtifactTool prompt (name + description) **Files:** - Create: `packages/builtin-tools/src/tools/ArtifactTool/prompt.ts` - [ ] **Step 1: Write prompt file** ```typescript // packages/builtin-tools/src/tools/ArtifactTool/prompt.ts export const ARTIFACT_TOOL_NAME = 'artifact' export async function describeArtifactTool(): Promise { return 'Upload an HTML file to the cloud-artifacts hosting service and get back a public URL. Pass `hash` to overwrite a previously-uploaded artifact (keeps URL stable).' } export async function getArtifactToolPrompt(): Promise { return `Upload an HTML file to a public hosting service and return a shareable URL plus an internal \`id\` (the "hash"). ## Inputs - \`file_path\` (required): absolute path to a local HTML file. - \`hash\` (optional): if provided, overwrites the artifact with the same hash (URL stays the same). If omitted, a new random id is generated. - \`ttl\` (optional, default \`7\`): artifact lifetime in days. Must be \`7\` or \`30\`. ## Output \`{ id, url, expiresAt }\` — \`id\` is the hash (save it for future overwrite calls), \`url\` is publicly accessible. ## Workflow 1. Use the Write tool to create a local HTML file. 2. Call this tool with its \`file_path\`. 3. If iterating on the same artifact, pass back the \`id\` returned from the first call as \`hash\` so the URL stays stable. ## Errors The tool surfaces backend error codes verbatim (e.g. \`payload_too_large\`, \`unauthorized\`). If the file does not exist or is not a regular file, the tool returns an \`error\` field without making an HTTP request.` } ``` - [ ] **Step 2: Commit** ```bash git add packages/builtin-tools/src/tools/ArtifactTool/prompt.ts git commit -m "feat(artifact): add tool name, description, and prompt" ``` --- ## Task 4: ArtifactTool definition (schema + call + render + map) **Files:** - Create: `packages/builtin-tools/src/tools/ArtifactTool/ArtifactTool.ts` - [ ] **Step 1: Write the tool definition** ```typescript // packages/builtin-tools/src/tools/ArtifactTool/ArtifactTool.ts import { stat, readFile } from 'fs/promises' import { z } from 'zod/v4' import type { ToolResultBlockParam } from 'src/Tool.js' import { buildTool } from 'src/Tool.js' import { lazySchema } from 'src/utils/lazySchema.js' import { ARTIFACT_TOOL_NAME, describeArtifactTool, getArtifactToolPrompt } from './prompt.js' import { getArtifactsToken, getUploadUrl } from './config.js' import { uploadArtifact } from './client.js' const inputSchema = lazySchema(() => z.strictObject({ file_path: z.string().describe('Absolute path to a local HTML file to upload.'), hash: z .string() .regex(/^[A-Za-z0-9_-]{1,128}$/, 'must match ^[A-Za-z0-9_-]{1,128}$') .optional() .describe('If provided, overwrites the existing artifact with this hash (URL stays stable). If omitted, a new random id is generated.'), ttl: z.union([z.literal(7), z.literal(30)]).default(7).describe('Lifetime in days. Must be 7 or 30. Default 7.'), }), ) type InputSchema = ReturnType type ArtifactInput = z.infer const outputSchema = lazySchema(() => z.object({ id: z.string(), url: z.string(), expiresAt: z.string(), }), ) type OutputSchema = ReturnType type ArtifactOutput = z.infer type ArtifactErrorOutput = ArtifactOutput & { error?: string } export const ArtifactTool = buildTool({ name: ARTIFACT_TOOL_NAME, searchHint: 'upload html artifact share url cloud publish progress report public link', maxResultSizeChars: 2_000, shouldDefer: true, strict: true, get inputSchema(): InputSchema { return inputSchema() }, get outputSchema(): OutputSchema { return outputSchema() }, async description() { return describeArtifactTool() }, async prompt() { return getArtifactToolPrompt() }, isEnabled() { return true }, isConcurrencySafe() { return false }, isReadOnly() { return false }, requiresUserInteraction() { return true }, userFacingName() { return 'Artifact' }, renderToolUseMessage(input: Partial) { const hashPart = input.hash ? ` (hash=${input.hash})` : '' return `Upload artifact: ${input.file_path ?? '...'}${hashPart}` }, mapToolResultToToolResultBlockParam(content: ArtifactErrorOutput, toolUseID: string): ToolResultBlockParam { if (content.error) { return { tool_use_id: toolUseID, type: 'tool_result', is_error: true, content: content.error, } } return { tool_use_id: toolUseID, type: 'tool_result', content: `Artifact uploaded: ${content.url} (id: ${content.id}, expires: ${content.expiresAt})`, } }, async call(input: ArtifactInput) { const { file_path, hash, ttl } = input let size: number try { const fileStat = await stat(file_path) if (!fileStat.isFile()) { return { data: { id: '', url: '', expiresAt: '', error: `Path is not a regular file: ${file_path}` } } } size = fileStat.size } catch { return { data: { id: '', url: '', expiresAt: '', error: `File does not exist or is not readable: ${file_path}` } } } if (size > 10 * 1024 * 1024) { return { data: { id: '', url: '', expiresAt: '', error: `File is ${size} bytes; backend limit is 10MB.` } } } let html: string try { html = await readFile(file_path, 'utf8') } catch { return { data: { id: '', url: '', expiresAt: '', error: `Failed to read file: ${file_path}` } } } try { const result = await uploadArtifact({ html, token: getArtifactsToken(), uploadUrl: getUploadUrl(), hash, ttl, }) return { data: result } } catch (e) { const message = e instanceof Error ? e.message : String(e) return { data: { id: '', url: '', expiresAt: '', error: message } } } }, }) ``` - [ ] **Step 2: Commit** ```bash git add packages/builtin-tools/src/tools/ArtifactTool/ArtifactTool.ts git commit -m "feat(artifact): add buildTool definition with file validation" ``` --- ## Task 5: Tool end-to-end tests **Files:** - Test: `packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts` - [ ] **Step 1: Write the e2e tool test** ```typescript // packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts import { existsSync, mkdirSync, rmSync, writeFileSync } from 'fs' import { tmpdir } from 'os' import { join } from 'path' import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test' import { ArtifactTool } from '../ArtifactTool.js' const TEST_DIR = join(tmpdir(), 'artifact-tool-test') const TEST_FILE = join(TEST_DIR, 'report.html') const MISSING_FILE = join(TEST_DIR, 'does-not-exist.html') const DIR_AS_FILE = TEST_DIR const originalFetch = globalThis.fetch function mockFetchSuccess(body: object): typeof fetch { return mock(() => Promise.resolve( new Response(JSON.stringify(body), { status: 200, headers: { 'Content-Type': 'application/json' }, }), ), ) as unknown as typeof fetch } describe('ArtifactTool.call', () => { beforeEach(() => { mkdirSync(TEST_DIR, { recursive: true }) writeFileSync(TEST_FILE, '

test report

', 'utf8') process.env.CLAUDE_ARTIFACTS_TOKEN = 'test-token' process.env.CLAUDE_ARTIFACTS_URL = 'https://example.test' }) afterEach(() => { if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true, force: true }) delete process.env.CLAUDE_ARTIFACTS_TOKEN delete process.env.CLAUDE_ARTIFACTS_URL globalThis.fetch = originalFetch }) test('uploads existing HTML file and returns id/url/expiresAt', async () => { globalThis.fetch = mockFetchSuccess({ id: 'abc123', url: 'https://example.test/7d/abc123.html', expiresAt: '2026-06-27T10:00:00.000Z', }) const result = await ArtifactTool.call({ file_path: TEST_FILE, ttl: 7 }, {} as never, {} as never, {} as never) expect(result.data).toMatchObject({ id: 'abc123', url: 'https://example.test/7d/abc123.html', expiresAt: '2026-06-27T10:00:00.000Z', }) expect((result.data as { error?: string }).error).toBeUndefined() }) test('passes hash through when overwriting', async () => { const fetchMock = mockFetchSuccess({ id: 'stable-id', url: 'https://example.test/7d/stable-id.html', expiresAt: '2026-06-27T10:00:00.000Z', }) globalThis.fetch = fetchMock await ArtifactTool.call({ file_path: TEST_FILE, hash: 'stable-id', ttl: 7 }, {} as never, {} as never, {} as never) const calledUrl = (fetchMock as unknown as { mock: { calls: [string | URL | Request][] } }).mock.calls[0][0] expect(calledUrl.toString()).toContain('hash=stable-id') }) test('returns error when file does not exist (no HTTP call)', async () => { let fetchCalled = false globalThis.fetch = mock(() => { fetchCalled = true return Promise.resolve(new Response('{}')) }) as unknown as typeof fetch const result = await ArtifactTool.call({ file_path: MISSING_FILE, ttl: 7 }, {} as never, {} as never, {} as never) expect(fetchCalled).toBe(false) expect((result.data as { error?: string }).error).toContain('does not exist') }) test('returns error when path is a directory', async () => { const result = await ArtifactTool.call({ file_path: DIR_AS_FILE, ttl: 7 }, {} as never, {} as never, {} as never) expect((result.data as { error?: string }).error).toContain('not a regular file') }) test('returns error verbatim when backend rejects', async () => { globalThis.fetch = mock(() => Promise.resolve(new Response(JSON.stringify({ error: 'payload_too_large' }), { status: 200 })), ) as unknown as typeof fetch // Force the size guard to pass by writing a small file but having backend complain. const result = await ArtifactTool.call({ file_path: TEST_FILE, ttl: 7 }, {} as never, {} as never, {} as never) expect((result.data as { error?: string }).error).toContain('payload_too_large') }) }) ``` - [ ] **Step 2: Run the e2e test** ```bash bun test packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts ``` Expected: PASS (5 tests). - [ ] **Step 3: Commit** ```bash git add packages/builtin-tools/src/tools/ArtifactTool/__tests__/ArtifactTool.test.ts git commit -m "test(artifact): add end-to-end tool tests for upload/error paths" ``` --- ## Task 6: Export ArtifactTool from builtin-tools barrel **Files:** - Modify: `packages/builtin-tools/src/index.ts` - [ ] **Step 1: Read barrel to find an insertion point** ```bash grep -n "SendUserFile" packages/builtin-tools/src/index.ts ``` - [ ] **Step 2: Add the export (insert after the SendUserFileTool line, keep alphabetical/grouped ordering)** Add this single line next to the other tool exports: ```typescript export { ArtifactTool } from './tools/ArtifactTool/ArtifactTool.js' ``` - [ ] **Step 3: Verify export works** ```bash bun -e "import('@claude-code-best/builtin-tools').then(m => console.log(typeof m.ArtifactTool))" ``` Expected output: `object` (the built tool). - [ ] **Step 4: Commit** ```bash git add packages/builtin-tools/src/index.ts git commit -m "feat(artifact): export ArtifactTool from builtin-tools barrel" ``` --- ## Task 7: Register ArtifactTool in src/tools.ts **Files:** - Modify: `src/tools.ts` - [ ] **Step 1: Add the require import (place near other non-feature-gated tools)** Find a clean spot in the top section (near other `const X = require(...)` declarations) and add: ```typescript const ArtifactTool = require('@claude-code-best/builtin-tools/tools/ArtifactTool/ArtifactTool.js').ArtifactTool ``` - [ ] **Step 2: Spread into the tools array (find the main returned array and add ArtifactTool unconditionally)** Add `ArtifactTool,` to the array literal that returns the assembled tool list (e.g. next to `BriefTool,`). - [ ] **Step 3: Verify by importing** ```bash bun -e "import('./src/tools.js').then(m => { const t = (m.getTools ?? m.tools); const arr = typeof t === 'function' ? t({mode:'default',additionalWorkingDirectories:new Set(),alwaysAllowRules:{deny:[],allow:[]},alwaysDenyRules:{deny:[],allow:[]},alwaysAskRules:{deny:[],allow:[]},isBypassPermissionsModeAvailable:false}) : t; console.log(arr.map(x=>x.name).includes('artifact')) })" ``` If the dynamic shape is hard to invoke, instead just typecheck: ```bash bunx tsc --noEmit -p tsconfig.json 2>&1 | head -50 ``` Expected: no new errors mentioning ArtifactTool. - [ ] **Step 4: Commit** ```bash git add src/tools.ts git commit -m "feat(artifact): register ArtifactTool in tools list" ``` --- ## Task 8: /use-artifacts bundled skill **Files:** - Create: `src/skills/bundled/useArtifacts.ts` - Modify: `src/skills/bundled/index.ts` - [ ] **Step 1: Write the skill file** ```typescript // src/skills/bundled/useArtifacts.ts import { registerBundledSkill } from '../bundledSkills.js' const USE_ARTIFACTS_PROMPT = `# Using Artifacts Artifacts are public HTML pages you upload to a hosting service. They have stable URLs that you can share with the user or open in a browser. Use them to surface work-in-progress, summaries, and reports. ## When to use artifacts **Good artifact content:** - Progress panels / kanbans (task list with status) - Research reports and analysis (data + findings + recommendations) - Design docs / decision records (with context and rationale) - Data visualizations (tables, SVG charts, flow diagrams) - Final deliverables (the "thing the user asked for" rendered as HTML) **Do NOT use artifacts for:** - Code snippets — use files directly - One-line answers — keep them in chat - Internal debug logs — keep them in chat - Large data dumps — link to source files instead ## Cadence — when to upload - **Task start**: if the task is complex (multi-step, research, deliverable), upload a skeleton artifact first as scaffolding (placeholder sections). - **Milestones**: when you complete a phase (research done / implementation done / tests pass), update the artifact. - **User asks**: upload immediately. - **Task end**: ship the final artifact as the deliverable. **Do NOT upload:** - After every tool call (noise) - Mid-step with no meaningful change (e.g. fixed a typo) ## How to invoke (deferred tool) \`artifact\` is a deferred tool. The first call requires two steps; subsequent calls one step. **First upload (creates a new artifact):** \`\`\` 1. Use the Write tool to write HTML to a local file (location is your choice). 2. SearchExtraTools({ query: "select:artifact" }) // loads the tool schema 3. ExecuteExtraTool({ tool_name: "artifact", params: { file_path: ".html" } }) 4. Save the returned \`id\` from the tool result — this is the hash. \`\`\` **Subsequent updates (overwrites in place, URL stays stable):** \`\`\` 1. Update the local HTML file. 2. ExecuteExtraTool({ tool_name: "artifact", params: { file_path: ".html", hash: "" } }) \`\`\` The URL returned on every call is the same when you pass the same \`hash\`. The user can open it at any time to see the latest version. ## Minimal HTML skeleton \`\`\`html Artifact Title

Artifact Title

\`\`\` The hosting service serves the HTML verbatim (including any \`