feat(templates): implement Templates as Tiles system#206
Conversation
Complete implementation of tile-based template storage with 6 subtasks:
1. Extend itemType to accept string values
- Add type guards: isBuiltInItemType(), isReservedItemType(), isCustomItemType()
- Add RESERVED_ITEM_TYPES constant
- 33 unit tests
2. Add templateName column
- Migration 0015_add_template_name_column.sql
- Update types across layers (domain → infrastructure → API)
- 19 integration tests
3. Implement Template Resolver Service
- TemplateResolverService for tile-based template lookup
- TemplateData and TemplateWithChildren types
- TemplateNotFoundError for clear error handling
- 25 unit tests
4. Migrate Built-in Templates to Tile Storage
- Seed script: drizzle/seeds/templates.seed.ts (idempotent)
- Well-known coordinates under D1i4gEqbi01JWS2F6I7GUN8ekRiU2mjK,0:1,2,*
- 33 unit tests
5. Update buildPrompt to Use Tile-Based Templates
- Tile-based template lookup with TypeScript fallback
- {{@ChildTemplateName}} pre-processor syntax for sub-templates
- 43 unit tests
6. Add User Template Allowlist Enforcement
- TemplateAllowlistService for security validation
- Built-in templates always allowed
- Case-insensitive matching
- Visibility validation (public tiles require public templates)
- 56 unit tests
Architecture improvements (Rule-of-6 compliance):
- services/_templates/ subfolder for template services
- services/_context/ subfolder for context builders
- templates/_internals/ for shared utilities
Total: 209+ new tests
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds "Templates as Tiles" and prompt-provenance features: DB template_name column, template seeding and migrations, per-user template allowlists, TemplateResolver and TemplateAllowlist services with Drizzle repo, a two‑phase template compiler/pool/pre‑processor, itemType extension to ItemTypeValue (built‑in/custom/reserved) and many type/export/UI adjustments. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant UI as Frontend (Tile UI / Prompt Builder)
participant Agentic as Agentic Service
participant Resolver as Template Resolver
participant Allowlist as Template Allowlist Service
participant Repo as DB Repository
participant Compiler as Template Compiler
User->>UI: request build/render (templateName, userId, itemType)
UI->>Agentic: buildPrompt(input)
Agentic->>Resolver: getTemplateByName(templateName)
Resolver->>Repo: findByTemplateName(templateName)
Repo-->>Resolver: TemplateData
Resolver-->>Agentic: TemplateData
Agentic->>Allowlist: validateAllowlist(userId, templateName)
Allowlist->>Repo: getUserAllowlist(userId)
Repo-->>Allowlist: UserAllowlist | null
Allowlist-->>Agentic: allowed | throws
Agentic->>Allowlist: validateVisibility(templateName, tileVisibility, templateVisibility)
Allowlist->>Repo: getTemplateVisibility(templateName)
Repo-->>Allowlist: Visibility
Allowlist-->>Agentic: ok | throws
Agentic->>Compiler: twoPhaseRender(templateTile, taskTile, fallback)
Compiler->>Compiler: compileTemplate (phase 1)
Compiler->>Compiler: buildMustacheData & render (phase 2)
Compiler-->>Agentic: rendered prompt
Agentic-->>UI: rendered prompt
UI-->>User: display rendered prompt
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Fix all issues with AI Agents 🤖
In @docs/features/PROMPT_PROVENANCE_UI.md:
- Around line 1-294: The doc incorrectly lives in the shipped/features area and
implies implemented APIs; move the design doc out of the public "features"
listing into the planned/features area (or otherwise mark it as "Planned") and
update the document text to clearly state it's a design proposal (not
implemented). Remove or clarify any language that presents APIs as existing
(references to AnnotatedPrompt, PromptSection, buildAnnotatedPrompt,
PromptViewer) so it doesn't read like shipped code, and if there is a FEATURES
index or sidebar update include this doc under "Planned" rather than active
features.
In @drizzle/seeds/templates.seed.ts:
- Around line 141-168: The _createTemplate function performs two separate
inserts (into vde_base_items and vde_map_items) without a transaction, risking
orphaned vde_base_items if the second insert fails; wrap both INSERTs in a
single database transaction (begin, run first INSERT capturing
baseResult/baseItemId, then second INSERT using parentId, SYSTEM_USER_ID,
TEMPLATE_ITEM_TYPE, spec.templateName, etc.), commit on success and rollback on
any error, and rethrow the error so callers can handle failures.
In
@src/lib/domains/agentic/services/__tests__/template-allowlist.service.test.ts:
- Around line 270-280: The test claims "allow built-in templates for any
visibility" but only asserts public/public cases; update the test in
template-allowlist.service.test.ts (the it block that calls
service.validateVisibility) to also assert that built-in templates (e.g.,
template types like 'system', 'user', 'organizational', 'context') do not throw
when passed private visibility combinations (for example 'private' or other
non-public visibility strings used in your codebase) by adding
expect(...validateVisibility(..., 'private', ...)).resolves.not.toThrow() checks
alongside the existing public checks so both public and private visibility cases
are covered.
In @src/lib/domains/agentic/templates/README.md:
- Around line 115-137: The fenced code blocks showing the directory tree and
template diagram in README.md (the blocks beginning with the templates/ listing
and the "Template \"my-agent-template\" (parent)" section) lack language
specifiers; update those triple-backtick fences to use a plain text specifier
such as ```text or ```plaintext to improve rendering and syntax highlighting for
the diagram blocks referenced in the README examples (the templates/ tree block
and the Template "my-agent-template" block).
🧹 Nitpick comments (17)
src/lib/domains/agentic/services/sandbox-session/session-store-factory.ts (1)
15-30: Consider error handling for Redis instantiation.The Redis client is instantiated without error handling. If the connection fails (e.g., invalid credentials, network issues), the error will propagate to the caller. Consider whether fail-fast is the desired behavior or if fallback to
MemorySessionStorewould be more resilient.🔎 Example: Add try-catch with fallback
if (redisUrl && redisToken) { - console.log('[SessionStore] Using Redis-backed session store') - const redis = new Redis({ - url: redisUrl, - token: redisToken - }) - return new RedisSessionStore(redis, defaultTtlSeconds) + try { + console.log('[SessionStore] Using Redis-backed session store') + const redis = new Redis({ + url: redisUrl, + token: redisToken + }) + return new RedisSessionStore(redis, defaultTtlSeconds) + } catch (error) { + console.error('[SessionStore] Failed to initialize Redis, falling back to memory store', error) + } } console.log('[SessionStore] Redis not configured, using in-memory store (sessions will not persist across cold starts)') return new MemorySessionStore()drizzle/migrations/0015_add_template_name_column.sql (1)
12-13: Consider uncommenting the index for template-based queries.The commented-out index on
template_namewould benefit queries filtering tiles by template (e.g., "show all tiles created from template X"). If template-based lookups are part of the feature set, the index should be created.🔎 Considerations for index creation
When to create the index:
- If the application queries tiles by
template_name(e.g.,WHERE template_name = ?)- If template-based reporting/analytics are planned
- If the "created from template X" feature mentioned in comments will be exposed in the UI
When to defer:
- If template-based queries are rare
- If most queries are by primary key or other indexed columns
- If table size is small enough that full scans are acceptable
Since the PR objectives mention "provenance tracking" and features like "created-from-template tracking," the index is likely needed. Consider uncommenting line 13.
src/server/db/schema/_tables/mapping/map-items.ts (1)
54-54: LGTM! Template provenance column added correctly.The
templateNamecolumn is properly configured:
- Nullable for backward compatibility with existing items
- Appropriate length (255) for template names
- Aligns with the tile-based template system objectives
Optional: Consider adding an index if template-based queries are planned
If you anticipate queries that filter or group by template name (e.g., "find all items created with template X"), consider adding an index:
refItemIdx: index("map_item_ref_item_idx").on(table.refItemId), + templateNameIdx: index("map_item_template_name_idx").on(table.templateName), uniqueCoords: uniqueIndex("map_item_unique_coords_idx").on(This can be deferred until query patterns emerge.
src/lib/domains/mapping/infrastructure/map-item/types.ts (1)
39-39: Consider simplifying the optional nullable type.The
templateName?: string | nulldeclaration creates three possible states (undefined, null, or string). While this may be intentional, it's typically clearer to use either:
templateName?: string(optional, can be undefined)templateName: string | null(required but nullable)Since line 13 uses
string | nulland the field should be explicitly set during creation (even if to null), consider:- templateName?: string | null; + templateName: string | null;Alternatively, if undefined should be allowed during creation with a default of null applied at the DB layer, the current form is acceptable but should be documented.
src/lib/domains/agentic/templates/__tests__/builtin-templates.test.ts (1)
78-161: Replace specification tests with integration tests once seed implementation exists.Many tests in this suite are tautological and don't verify actual runtime behavior:
- Lines 80-91: Verifies a constant array equals itself
- Lines 137-148: Checks two hardcoded values are equal
- Lines 150-159: Compares the same constant to itself
While these serve as TDD specifications (as noted in the file header), they should be replaced with actual integration tests that verify the seed script's behavior against a real or test database once
drizzle/seeds/templates.seed.tsis implemented.Consider replacing these with tests that:
- Actually call the seed function with a test database
- Verify template tiles are created at the correct coordinates
- Validate idempotency by running the seed twice and checking for duplicates
- Confirm content synchronization when TypeScript templates change
src/lib/domains/mapping/infrastructure/map-item/README.md (1)
138-155: Consider clarifying item_type semantic types.The documentation states
item_typecontains "Semantic tile type (USER, ORGANIZATIONAL, CONTEXT, SYSTEM)", but according to the PR objectives (subtask 1),itemTypehas been extended to accept string values beyond built-in types, including custom types.Consider updating the description to:
-| `item_type` | varchar(50) | Semantic tile type (USER, ORGANIZATIONAL, CONTEXT, SYSTEM) | +| `item_type` | varchar(50) | Semantic tile type (built-in: USER, ORGANIZATIONAL, CONTEXT, SYSTEM; supports custom types) |This clarifies that the field accepts both built-in and custom type values.
src/lib/domains/agentic/templates/_pre-processor/index.ts (1)
123-146: Consider removing the unreachable default case.Line 144's default case is unreachable since
_validateTemplateExistsensures the template exists before_invokeTemplateis called. While this is defensive programming, it creates dead code.Options:
- Keep it as defensive programming (current approach)
- Remove it and rely on TypeScript exhaustiveness checking
- Add a comment explaining it's intentionally defensive
src/server/db/schema/__tests__/template-name-column.integration.test.ts (1)
1-7: Consider importing Drizzle operators at the module level.Lines 314, 342, 505, 532, and 576 use dynamic imports for
eq,and, andisNullfromdrizzle-orm. For better performance and readability, import these at the top of the file alongside other imports.🔎 Proposed fix
import { describe, beforeEach, it, expect } from "vitest"; +import { eq, and, isNull } from "drizzle-orm"; import { db } from "~/server/db"; import { schema } from "~/server/db";Then update the dynamic imports throughout the file:
- const { eq } = await import("drizzle-orm"); const [updatedItem] = await dbsrc/lib/domains/mapping/infrastructure/map-item/__tests__/item-type-extension.test.ts (1)
4-10: Unnecessary eslint-disable comment.The
@typescript-eslint/no-unused-varsdisable is not needed since all imported items (isBuiltInItemType,isReservedItemType,isCustomItemType,RESERVED_ITEM_TYPES) are used throughout the test file.🔎 Suggested fix
-// eslint-disable-next-line @typescript-eslint/no-unused-vars import { isBuiltInItemType, isReservedItemType, isCustomItemType, RESERVED_ITEM_TYPES, } from "~/lib/domains/mapping/infrastructure/map-item/item-type-utils";src/lib/domains/agentic/templates/_internals/types.ts (1)
22-28: Consider unifying ancestor type with PromptDataTile.The
ancestorsarray type is nearly identical toPromptDataTile(minuspreviewandchildren). For consistency and potential future extensibility, consider reusing or extendingPromptDataTile:- /** Ancestors from root to parent - content flows top-down */ - ancestors: Array<{ - title: string - content: string | undefined - coords: string - itemType?: MapItemType - }> + /** Ancestors from root to parent - content flows top-down */ + ancestors: Array<Omit<PromptDataTile, 'preview' | 'children'>>This reduces type duplication and makes it clearer that ancestors follow the same shape as tiles.
src/lib/domains/agentic/services/__tests__/template-allowlist.service.test.ts (1)
400-413: Performance test lacks timing assertion.The test claims to verify large allowlists are handled "efficiently" but only checks correctness, not performance. Consider adding a timing bound or documenting that this is a correctness-under-load test.
it('should handle user with large allowlist efficiently', async () => { const largeAllowlist: UserAllowlist = { userId: testUserId, allowedTemplates: Array.from({ length: 1000 }, (_, i) => `template-${i}`) } vi.mocked(mockRepository.getUserAllowlist).mockResolvedValue(largeAllowlist) - // Should still perform efficiently + // Verify correctness with large dataset (performance is O(n) with Set lookup) await expect(service.validateAllowlist(testUserId, 'template-999')) .resolves.not.toThrow() await expect(service.validateAllowlist(testUserId, 'template-not-in-list')) .rejects .toThrow(TemplateNotAllowedError) })src/lib/domains/mapping/infrastructure/map-item/queries/specialized-queries.ts (1)
390-435: Type annotations could be more precise.The
childrenResultsandgrandchildrenResultsarrays are typed asArray<{ map_items: unknown; base_items: unknown }>, requiring type guards and casts later. Consider defining a more precise intermediate type to improve type safety.// Define a result type matching the select shape type ContextQueryResult = { map_items: { id: number; coord_user_id: string; coord_group_id: number; path: string; item_type: string; visibility: string; parentId: number | null; refItemId: number; templateName: string | null; createdAt: Date; updatedAt: Date; }; base_items: { id: number; title: string; content: string; preview: string | null; link: string | null; originId: string | null; createdAt: Date; updatedAt: Date; } | null; }; // Then use: let childrenResults: ContextQueryResult[] = [];src/lib/domains/agentic/templates/__tests__/prompt-builder-tile-templates.test.ts (1)
241-251: Circular reference test is a placeholder.This test only verifies that a string equals itself, which always passes. When implementing the actual circular detection, ensure this test exercises the real detection logic.
it('should detect circular template references and throw error', async () => { // GIVEN template A references B and B references A // A: <a>{{@TemplateB}}</a> // B: <b>{{@TemplateA}}</b> // WHEN the template is pre-processed // THEN it should detect the cycle and throw a clear error - const expectedErrorType = 'CircularTemplateError' - expect(expectedErrorType).toBe('CircularTemplateError') + // TODO: Replace with actual implementation test + // Example: + // await expect(preprocessTemplate(circularTemplateA, mockStore)) + // .rejects.toThrow(CircularTemplateError) + expect(true).toBe(true) // Placeholder until implementation })src/lib/domains/agentic/services/__tests__/template-resolver.service.test.ts (1)
167-214: Test name is misleading - tests sibling count, not nesting depth.The test is named "should handle deeply nested sub-template structures" but actually tests 6 sibling sub-templates at the same level, not nested depth. Consider renaming to clarify intent.
🔎 Suggested rename
- it('should handle deeply nested sub-template structures', async () => { + it('should handle maximum structural children (6 sub-templates)', async () => {docs/features/TEMPLATES_AS_TILES.md (2)
69-74: Add language specifier to fenced code block.Per markdownlint, fenced code blocks should have a language specified. This appears to be a plain text hierarchical structure.
🔎 Proposed fix
-``` +```text Template "my-agent-template" ├── [1] Sub-template "HeaderSection" ├── [2] Sub-template "ContextBlock" └── [3] Sub-template "TaskSection" ```
185-205: Add language specifier to fenced code block.This workflow description should have a language specifier for markdown consistency.
🔎 Proposed fix
-``` +```text 1. User creates tile: - type: "template" ...src/lib/domains/agentic/services/_templates/template-allowlist.service.ts (1)
45-68: Consider adding prototype chain fix for Error subclasses.Same as
TemplateNotFoundError, these error classes may haveinstanceofissues in transpiled code.🔎 Proposed fix for both error classes
export class TemplateNotAllowedError extends Error { constructor( public readonly templateName: string, public readonly userId: string | null, public readonly allowedTemplates: string[] ) { super(`Template "${templateName}" is not allowed for user "${userId}"`) this.name = 'TemplateNotAllowedError' + Object.setPrototypeOf(this, TemplateNotAllowedError.prototype) } } export class TemplateVisibilityError extends Error { constructor( public readonly templateName: string, public readonly tileVisibility: Visibility, public readonly templateVisibility: Visibility ) { super(`Cannot use private template "${templateName}" for public tile`) this.name = 'TemplateVisibilityError' + Object.setPrototypeOf(this, TemplateVisibilityError.prototype) } }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (57)
CLAUDE.mddocs/features/PROMPT_PROVENANCE_UI.mddocs/features/TEMPLATES_AS_TILES.mddrizzle/migrations/0015_add_template_name_column.sqldrizzle/seeds/templates.seed.tssrc/.ruleof6-exceptionssrc/lib/domains/agentic/index.tssrc/lib/domains/agentic/repositories/_helpers/stream-event-extractors.tssrc/lib/domains/agentic/repositories/claude-agent-sdk.repository.tssrc/lib/domains/agentic/services/README.mdsrc/lib/domains/agentic/services/__tests__/agentic.service.test.tssrc/lib/domains/agentic/services/__tests__/canvas-context-builder.test.tssrc/lib/domains/agentic/services/__tests__/chat-context-builder.test.tssrc/lib/domains/agentic/services/__tests__/context-composition.test.tssrc/lib/domains/agentic/services/__tests__/context-serializer.test.tssrc/lib/domains/agentic/services/__tests__/template-allowlist.service.test.tssrc/lib/domains/agentic/services/__tests__/template-resolver.service.test.tssrc/lib/domains/agentic/services/_context/canvas-context-builder.service.tssrc/lib/domains/agentic/services/_context/chat-context-builder.service.tssrc/lib/domains/agentic/services/_context/context-composition.service.tssrc/lib/domains/agentic/services/_context/context-serializer.service.tssrc/lib/domains/agentic/services/_context/tokenizer.service.tssrc/lib/domains/agentic/services/_templates/index.tssrc/lib/domains/agentic/services/_templates/prompt-template.service.tssrc/lib/domains/agentic/services/_templates/template-allowlist.service.tssrc/lib/domains/agentic/services/_templates/template-resolver.service.tssrc/lib/domains/agentic/services/agentic.factory.tssrc/lib/domains/agentic/services/agentic.service.tssrc/lib/domains/agentic/services/index.tssrc/lib/domains/agentic/services/preview-generator.service.tssrc/lib/domains/agentic/services/sandbox-session/index.tssrc/lib/domains/agentic/services/sandbox-session/redis-session-store.tssrc/lib/domains/agentic/services/sandbox-session/session-store-factory.tssrc/lib/domains/agentic/templates/README.mdsrc/lib/domains/agentic/templates/__tests__/builtin-templates.test.tssrc/lib/domains/agentic/templates/__tests__/prompt-builder-tile-templates.test.tssrc/lib/domains/agentic/templates/_internals/section-builders.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/templates/_internals/utils.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/agentic/templates/_prompt-builder.tssrc/lib/domains/agentic/templates/_templates/_folder.tssrc/lib/domains/agentic/templates/_templates/_generic-tile.tssrc/lib/domains/agentic/templates/_templates/_hexplan.tssrc/lib/domains/mapping/README.mdsrc/lib/domains/mapping/_objects/README.mdsrc/lib/domains/mapping/_objects/map-item.tssrc/lib/domains/mapping/infrastructure/map-item/README.mdsrc/lib/domains/mapping/infrastructure/map-item/__tests__/item-type-extension.test.tssrc/lib/domains/mapping/infrastructure/map-item/db.tssrc/lib/domains/mapping/infrastructure/map-item/item-type-utils.tssrc/lib/domains/mapping/infrastructure/map-item/mappers.tssrc/lib/domains/mapping/infrastructure/map-item/queries/specialized-queries.tssrc/lib/domains/mapping/infrastructure/map-item/queries/write-queries.tssrc/lib/domains/mapping/infrastructure/map-item/types.tssrc/server/db/schema/__tests__/template-name-column.integration.test.tssrc/server/db/schema/_tables/mapping/map-items.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always use absolute imports with
~/prefix instead of relative imports (./or../), as enforced by ESLintno-restricted-importsrule
Files:
src/lib/domains/mapping/infrastructure/map-item/queries/write-queries.tssrc/lib/domains/agentic/templates/_internals/utils.tssrc/lib/domains/agentic/services/agentic.factory.tssrc/lib/domains/mapping/_objects/map-item.tssrc/lib/domains/agentic/templates/__tests__/builtin-templates.test.tssrc/lib/domains/agentic/services/__tests__/template-allowlist.service.test.tssrc/server/db/schema/__tests__/template-name-column.integration.test.tssrc/lib/domains/agentic/templates/_templates/_generic-tile.tssrc/lib/domains/agentic/repositories/claude-agent-sdk.repository.tssrc/lib/domains/mapping/infrastructure/map-item/queries/specialized-queries.tssrc/lib/domains/agentic/services/__tests__/context-serializer.test.tssrc/lib/domains/agentic/services/__tests__/template-resolver.service.test.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/mapping/infrastructure/map-item/item-type-utils.tssrc/lib/domains/agentic/templates/_templates/_folder.tssrc/lib/domains/agentic/services/sandbox-session/session-store-factory.tssrc/lib/domains/agentic/services/_templates/index.tssrc/lib/domains/agentic/templates/_templates/_hexplan.tssrc/lib/domains/agentic/repositories/_helpers/stream-event-extractors.tssrc/lib/domains/agentic/services/__tests__/chat-context-builder.test.tssrc/lib/domains/agentic/services/_templates/template-resolver.service.tssrc/lib/domains/agentic/services/sandbox-session/index.tssrc/lib/domains/agentic/services/__tests__/agentic.service.test.tssrc/lib/domains/agentic/services/preview-generator.service.tssrc/lib/domains/agentic/templates/_internals/section-builders.tssrc/lib/domains/mapping/infrastructure/map-item/mappers.tssrc/lib/domains/agentic/services/__tests__/canvas-context-builder.test.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/services/index.tssrc/lib/domains/agentic/index.tssrc/lib/domains/mapping/infrastructure/map-item/types.tssrc/lib/domains/agentic/services/__tests__/context-composition.test.tssrc/server/db/schema/_tables/mapping/map-items.tssrc/lib/domains/agentic/templates/__tests__/prompt-builder-tile-templates.test.tsdrizzle/seeds/templates.seed.tssrc/lib/domains/agentic/services/_context/context-composition.service.tssrc/lib/domains/agentic/services/agentic.service.tssrc/lib/domains/mapping/infrastructure/map-item/db.tssrc/lib/domains/agentic/services/sandbox-session/redis-session-store.tssrc/lib/domains/mapping/infrastructure/map-item/__tests__/item-type-extension.test.tssrc/lib/domains/agentic/services/_templates/template-allowlist.service.tssrc/lib/domains/agentic/templates/_prompt-builder.ts
src/lib/domains/agentic/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
buildPrompt()function fromsrc/lib/domains/agentic/services/prompt-executor.service.tsto deterministically generate XML prompts from tile coordinates
Files:
src/lib/domains/agentic/templates/_internals/utils.tssrc/lib/domains/agentic/services/agentic.factory.tssrc/lib/domains/agentic/templates/__tests__/builtin-templates.test.tssrc/lib/domains/agentic/services/__tests__/template-allowlist.service.test.tssrc/lib/domains/agentic/templates/_templates/_generic-tile.tssrc/lib/domains/agentic/repositories/claude-agent-sdk.repository.tssrc/lib/domains/agentic/services/__tests__/context-serializer.test.tssrc/lib/domains/agentic/services/__tests__/template-resolver.service.test.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/agentic/templates/_templates/_folder.tssrc/lib/domains/agentic/services/sandbox-session/session-store-factory.tssrc/lib/domains/agentic/services/_templates/index.tssrc/lib/domains/agentic/templates/_templates/_hexplan.tssrc/lib/domains/agentic/repositories/_helpers/stream-event-extractors.tssrc/lib/domains/agentic/services/__tests__/chat-context-builder.test.tssrc/lib/domains/agentic/services/_templates/template-resolver.service.tssrc/lib/domains/agentic/services/sandbox-session/index.tssrc/lib/domains/agentic/services/__tests__/agentic.service.test.tssrc/lib/domains/agentic/services/preview-generator.service.tssrc/lib/domains/agentic/templates/_internals/section-builders.tssrc/lib/domains/agentic/services/__tests__/canvas-context-builder.test.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/services/index.tssrc/lib/domains/agentic/index.tssrc/lib/domains/agentic/services/__tests__/context-composition.test.tssrc/lib/domains/agentic/templates/__tests__/prompt-builder-tile-templates.test.tssrc/lib/domains/agentic/services/_context/context-composition.service.tssrc/lib/domains/agentic/services/agentic.service.tssrc/lib/domains/agentic/services/sandbox-session/redis-session-store.tssrc/lib/domains/agentic/services/_templates/template-allowlist.service.tssrc/lib/domains/agentic/templates/_prompt-builder.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Vitest for tests (not Jest)
Files:
src/lib/domains/agentic/templates/__tests__/builtin-templates.test.tssrc/lib/domains/agentic/services/__tests__/template-allowlist.service.test.tssrc/server/db/schema/__tests__/template-name-column.integration.test.tssrc/lib/domains/agentic/services/__tests__/context-serializer.test.tssrc/lib/domains/agentic/services/__tests__/template-resolver.service.test.tssrc/lib/domains/agentic/services/__tests__/chat-context-builder.test.tssrc/lib/domains/agentic/services/__tests__/agentic.service.test.tssrc/lib/domains/agentic/services/__tests__/canvas-context-builder.test.tssrc/lib/domains/agentic/services/__tests__/context-composition.test.tssrc/lib/domains/agentic/templates/__tests__/prompt-builder-tile-templates.test.tssrc/lib/domains/mapping/infrastructure/map-item/__tests__/item-type-extension.test.ts
src/server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use tRPC for type-safe API in backend implementations
Files:
src/server/db/schema/__tests__/template-name-column.integration.test.tssrc/server/db/schema/_tables/mapping/map-items.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to src/lib/domains/agentic/**/*.{ts,tsx} : Use `buildPrompt()` function from `src/lib/domains/agentic/services/prompt-executor.service.ts` to deterministically generate XML prompts from tile coordinates
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to src/lib/domains/agentic/**/*.{ts,tsx} : Use `buildPrompt()` function from `src/lib/domains/agentic/services/prompt-executor.service.ts` to deterministically generate XML prompts from tile coordinates
Applied to files:
src/lib/domains/agentic/templates/_internals/utils.tssrc/lib/domains/agentic/services/agentic.factory.tsdocs/features/PROMPT_PROVENANCE_UI.mdsrc/lib/domains/agentic/templates/__tests__/builtin-templates.test.tssrc/lib/domains/agentic/services/README.mdsrc/lib/domains/agentic/templates/_templates/_generic-tile.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/agentic/templates/_templates/_folder.tssrc/lib/domains/agentic/services/_templates/index.tssrc/lib/domains/agentic/templates/_templates/_hexplan.tssrc/lib/domains/agentic/services/__tests__/chat-context-builder.test.tssrc/lib/domains/agentic/services/_templates/template-resolver.service.tssrc/lib/domains/agentic/services/__tests__/agentic.service.test.tssrc/lib/domains/agentic/services/preview-generator.service.tssrc/lib/domains/agentic/templates/_internals/section-builders.tssrc/lib/domains/agentic/services/__tests__/canvas-context-builder.test.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/services/index.tssrc/lib/domains/agentic/index.tsdocs/features/TEMPLATES_AS_TILES.mdsrc/lib/domains/agentic/templates/__tests__/prompt-builder-tile-templates.test.tssrc/lib/domains/agentic/templates/README.mdsrc/lib/domains/agentic/services/_context/context-composition.service.tssrc/lib/domains/agentic/services/agentic.service.tssrc/lib/domains/agentic/templates/_prompt-builder.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Every tile has a semantic type (USER, ORGANIZATIONAL, CONTEXT, SYSTEM) that guides agent behavior; treat unclassified legacy tiles with null itemType as undefined
Applied to files:
src/lib/domains/mapping/_objects/map-item.tsCLAUDE.mdsrc/lib/domains/mapping/README.mdsrc/lib/domains/mapping/_objects/README.md
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to **/*.{test,spec}.{js,jsx,ts,tsx} : Use Vitest for tests (not Jest)
Applied to files:
src/lib/domains/agentic/services/__tests__/context-serializer.test.tssrc/lib/domains/agentic/services/__tests__/chat-context-builder.test.tssrc/lib/domains/agentic/services/__tests__/agentic.service.test.tssrc/lib/domains/agentic/services/__tests__/canvas-context-builder.test.tssrc/lib/domains/agentic/services/__tests__/context-composition.test.tssrc/lib/domains/mapping/infrastructure/map-item/__tests__/item-type-extension.test.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Tiles can have both subtask children (positive directions 1-6) and context children (negative directions -1 to -6) simultaneously; context children are reference materials not preloaded eagerly
Applied to files:
CLAUDE.md
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Use `updateItem` calls for agents to update hexplan tiles with status markers (🟡, ✅, 🔴)
Applied to files:
CLAUDE.mdsrc/lib/domains/agentic/templates/_templates/_hexplan.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Hexplan tiles at direction-0 track execution state with status markers (🟡 STARTED, ✅ COMPLETED, 🔴 BLOCKED) and steps without prefixes are considered pending (📋)
Applied to files:
src/lib/domains/agentic/templates/_templates/_hexplan.ts
🧬 Code graph analysis (12)
src/lib/domains/agentic/templates/__tests__/builtin-templates.test.ts (4)
src/lib/domains/agentic/templates/_system-template.ts (1)
SYSTEM_TEMPLATE(37-58)src/lib/domains/agentic/templates/_user-template.ts (1)
USER_TEMPLATE(59-91)src/lib/domains/agentic/services/_templates/index.ts (3)
TemplateRepository(19-19)TemplateResolverService(13-13)TemplateData(17-17)src/lib/domains/agentic/services/_templates/template-resolver.service.ts (3)
TemplateRepository(45-48)TemplateResolverService(59-125)TemplateData(13-18)
src/server/db/schema/__tests__/template-name-column.integration.test.ts (2)
src/lib/domains/mapping/services/__tests__/helpers/_test-utilities.ts (1)
_createUniqueTestParams(176-191)src/server/db/schema/_tables/mapping/map-items.ts (1)
mapItems(38-91)
src/lib/domains/mapping/infrastructure/map-item/queries/specialized-queries.ts (4)
src/lib/domains/mapping/utils/index.ts (1)
Direction(10-10)src/app/map/constants.ts (1)
Direction(4-4)src/lib/domains/mapping/infrastructure/map-item/mappers.ts (1)
pathToString(33-35)src/server/db/schema/_tables/mapping/map-items.ts (1)
mapItems(38-91)
src/lib/domains/agentic/services/__tests__/template-resolver.service.test.ts (1)
src/lib/domains/agentic/services/_templates/template-resolver.service.ts (5)
TemplateRepository(45-48)TemplateResolverService(59-125)TemplateData(13-18)TemplateWithChildren(23-25)TemplateNotFoundError(32-37)
src/lib/domains/agentic/templates/_pre-processor/index.ts (3)
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (2)
resolveItemReference(56-88)TemplateContext(20-41)src/lib/domains/agentic/templates/_pre-processor/_parser.ts (1)
ParsedParams(16-22)src/lib/domains/agentic/templates/_templates/index.ts (1)
TemplateRegistry(15-41)
src/lib/domains/agentic/services/sandbox-session/session-store-factory.ts (1)
src/lib/domains/agentic/services/sandbox-session/redis-session-store.ts (2)
ISessionStore(8-13)MemorySessionStore(19-37)
src/lib/domains/agentic/templates/_internals/section-builders.ts (4)
src/lib/domains/agentic/templates/_internals/types.ts (2)
PromptDataTile(7-14)PromptData(16-42)src/lib/domains/agentic/templates/_internals/utils.ts (2)
_hasContent(22-24)_escapeXML(10-17)src/lib/domains/agentic/templates/_templates/_generic-tile.ts (1)
GenericTile(53-95)src/lib/domains/agentic/templates/_system-template.ts (1)
ANCESTOR_INTRO(73-73)
src/lib/domains/agentic/templates/_internals/types.ts (1)
src/lib/domains/agentic/templates/_prompt-builder.ts (2)
PromptDataTile(29-29)PromptData(29-29)
src/lib/domains/agentic/templates/__tests__/prompt-builder-tile-templates.test.ts (3)
src/lib/domains/agentic/templates/_internals/types.ts (1)
PromptData(16-42)src/lib/domains/agentic/templates/_system-template.ts (1)
SYSTEM_TEMPLATE(37-58)src/lib/domains/agentic/templates/_user-template.ts (1)
USER_TEMPLATE(59-91)
drizzle/seeds/templates.seed.ts (2)
src/lib/domains/agentic/templates/_system-template.ts (1)
SYSTEM_TEMPLATE(37-58)src/lib/domains/agentic/templates/_user-template.ts (1)
USER_TEMPLATE(59-91)
src/lib/domains/mapping/infrastructure/map-item/__tests__/item-type-extension.test.ts (1)
src/lib/domains/mapping/infrastructure/map-item/item-type-utils.ts (4)
RESERVED_ITEM_TYPES(21-21)isBuiltInItemType(34-39)isReservedItemType(47-52)isCustomItemType(61-66)
src/lib/domains/agentic/services/_templates/template-allowlist.service.ts (1)
src/lib/domains/agentic/services/_templates/index.ts (7)
BUILT_IN_TEMPLATES(26-26)Visibility(29-29)UserAllowlist(30-30)TemplateNotAllowedError(24-24)TemplateVisibilityError(25-25)TemplateAllowlistRepository(31-31)TemplateAllowlistService(23-23)
🪛 markdownlint-cli2 (0.18.1)
docs/features/TEMPLATES_AS_TILES.md
69-69: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
185-185: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
src/lib/domains/agentic/templates/README.md
115-115: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
161-161: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Tests - Phase 2 (React Components)
- GitHub Check: Build
- GitHub Check: ESLint
- GitHub Check: Tests - Phase 1 (Main Suite)
| # Prompt Provenance UI | ||
|
|
||
| ## Summary | ||
|
|
||
| Show users exactly which tiles and templates contributed to each part of a generated prompt, enabling transparency and debuggability. | ||
|
|
||
| ## Motivation | ||
|
|
||
| **Current state:** The Chat can display the generated prompt, but it's an opaque text blob. Users cannot see: | ||
| - Which tile contributed which section | ||
| - Which template rendered it | ||
| - How to modify a specific part of the output | ||
|
|
||
| **Problem:** | ||
| - Debugging prompts requires mental reconstruction | ||
| - Users can't learn how their tile structure affects prompts | ||
| - No easy path from "I don't like this part" to "here's where to change it" | ||
|
|
||
| **Hexframe principle:** Transparency. Users should understand exactly how their system produces outputs. | ||
|
|
||
| ## Relationship to Templates as Tiles | ||
|
|
||
| This feature builds on [Templates as Tiles](./TEMPLATES_AS_TILES.md): | ||
|
|
||
| | Templates as Tiles provides | This feature uses it for | | ||
| |----------------------------|--------------------------| | ||
| | Templates have coordinates | Link prompt sections to template source | | ||
| | Sub-templates are children | Show template composition hierarchy | | ||
| | Dynamic tile types | Show which type produced each section | | ||
|
|
||
| ## Core Design | ||
|
|
||
| ### 1. Annotated Prompt Structure | ||
|
|
||
| The prompt builder returns both: | ||
| - **Raw prompt**: The text sent to the LLM (unchanged) | ||
| - **Annotated prompt**: Structured representation with provenance | ||
|
|
||
| ```typescript | ||
| interface AnnotatedPrompt { | ||
| raw: string; | ||
| sections: PromptSection[]; | ||
| } | ||
|
|
||
| interface PromptSection { | ||
| content: string; | ||
| source: { | ||
| tileCoords: string; // e.g., "userId,0:1,3" | ||
| tileTitle: string; | ||
| templateCoords?: string; // Template that rendered this tile | ||
| templateName?: string; | ||
| }; | ||
| children?: PromptSection[]; // Nested sections (sub-templates) | ||
| } | ||
| ``` | ||
|
|
||
| ### 2. Section Boundaries | ||
|
|
||
| Each discrete prompt section tracks its origin: | ||
|
|
||
| ``` | ||
| ┌─────────────────────────────────────────────────────────────┐ | ||
| │ <hexrun-intro> │ | ||
| │ This prompt was generated from Hexframe tiles... │ | ||
| │ │ | ||
| │ Source: Template "system" → HexrunIntro section │ | ||
| └─────────────────────────────────────────────────────────────┘ | ||
|
|
||
| ┌─────────────────────────────────────────────────────────────┐ | ||
| │ <context title="API Reference" coords="user,0:1,-2"> │ | ||
| │ The API uses REST conventions... │ | ||
| │ </context> │ | ||
| │ │ | ||
| │ Source: Tile "API Reference" at user,0:1,-2 │ | ||
| │ Rendered by: GenericTile primitive │ | ||
| └─────────────────────────────────────────────────────────────┘ | ||
|
|
||
| ┌─────────────────────────────────────────────────────────────┐ | ||
| │ <task> │ | ||
| │ <goal>Implement authentication</goal> │ | ||
| │ Add JWT-based auth to the API endpoints... │ | ||
| │ </task> │ | ||
| │ │ | ||
| │ Source: Tile "Implement authentication" at user,0:1,3 │ | ||
| │ Rendered by: Template "system" → TaskSection │ | ||
| └─────────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| ### 3. UI Presentation Options | ||
|
|
||
| #### Option A: Highlighted Blocks | ||
|
|
||
| Prompt displayed as text with colored/bordered blocks. Hovering shows source info. | ||
|
|
||
| ``` | ||
| ┌─ Template: system ──────────────────────────────────────────┐ | ||
| │ <hexrun-intro> │ | ||
| │ This prompt was generated from Hexframe tiles... │ | ||
| │ </hexrun-intro> │ | ||
| └─────────────────────────────────────────────────────────────┘ | ||
|
|
||
| ┌─ Tile: API Reference (user,0:1,-2) ─────────────────────────┐ | ||
| │ <context title="API Reference"> │ | ||
| │ The API uses REST conventions... │ | ||
| │ </context> │ | ||
| └─────────────────────────────────────────────────────────────┘ | ||
| ``` | ||
|
|
||
| **Interactions:** | ||
| - Hover: Show source tile/template info | ||
| - Click: Navigate to source tile in map | ||
| - Right-click: "Edit this tile" / "View template" | ||
|
|
||
| #### Option B: Side Panel | ||
|
|
||
| Prompt text on left, source tree on right. Selecting text highlights corresponding source. | ||
|
|
||
| ``` | ||
| ┌────────────────────────────┬─────────────────────────────┐ | ||
| │ <hexrun-intro> │ ▼ Prompt Structure │ | ||
| │ This prompt was generated │ ├─ HexrunIntro (template) │ | ||
| │ from Hexframe tiles... │ ├─ Context │ | ||
| │ </hexrun-intro> │ │ └─ API Reference │ | ||
| │ │ ├─ Subtasks │ | ||
| │ <context title="API Ref"> │ │ ├─ Task 1 │ | ||
| │ The API uses REST... │ │ └─ Task 2 │ | ||
| │ </context> │ ├─ Task │ | ||
| │ │ │ └─ Implement auth │ | ||
| │ <subtasks> │ └─ HexPlan (template) │ | ||
| │ ... │ │ | ||
| └────────────────────────────┴─────────────────────────────┘ | ||
| ``` | ||
|
|
||
| #### Option C: Inline Annotations (Collapsible) | ||
|
|
||
| Each section has a small annotation bar that expands on click. | ||
|
|
||
| ``` | ||
| ▸ Template: system/hexrun-intro ───────────────────────────── | ||
| <hexrun-intro> | ||
| This prompt was generated from Hexframe tiles... | ||
| </hexrun-intro> | ||
|
|
||
| ▸ Tile: API Reference ─────────────────────────────────────── | ||
| <context title="API Reference"> | ||
| The API uses REST conventions... | ||
| </context> | ||
| ``` | ||
|
|
||
| ### 4. Implementation Approach | ||
|
|
||
| #### Phase 1: Track Provenance in Prompt Builder | ||
|
|
||
| Modify `buildPrompt()` to return `AnnotatedPrompt`: | ||
|
|
||
| ```typescript | ||
| // Before | ||
| function buildPrompt(data: PromptData): string | ||
|
|
||
| // After | ||
| function buildPrompt(data: PromptData): AnnotatedPrompt | ||
| // or | ||
| function buildAnnotatedPrompt(data: PromptData): AnnotatedPrompt | ||
| ``` | ||
|
|
||
| Each section builder tracks its source: | ||
|
|
||
| ```typescript | ||
| function _buildContextSection(composedChildren, ...): PromptSection[] { | ||
| return composedChildren.map(child => ({ | ||
| content: GenericTile(child, ['title', 'content'], 'context'), | ||
| source: { | ||
| tileCoords: child.coords, | ||
| tileTitle: child.title, | ||
| // templateCoords if rendered by a tile-based template | ||
| } | ||
| })); | ||
| } | ||
| ``` | ||
|
|
||
| #### Phase 2: Store Provenance with Chat Messages | ||
|
|
||
| The annotated prompt is stored alongside messages: | ||
|
|
||
| ```typescript | ||
| interface ChatMessage { | ||
| role: 'user' | 'assistant' | 'system'; | ||
| content: string; | ||
| promptProvenance?: AnnotatedPrompt; // For system messages | ||
| } | ||
| ``` | ||
|
|
||
| #### Phase 3: Render in Chat UI | ||
|
|
||
| Add prompt viewer component with provenance display: | ||
|
|
||
| ```typescript | ||
| <PromptViewer | ||
| prompt={message.promptProvenance} | ||
| onTileClick={(coords) => navigateToTile(coords)} | ||
| onTemplateClick={(coords) => navigateToTemplate(coords)} | ||
| /> | ||
| ``` | ||
|
|
||
| ## Data Flow | ||
|
|
||
| ``` | ||
| Tile Hierarchy | ||
| │ | ||
| ▼ | ||
| ┌─────────────────┐ | ||
| │ buildPrompt() │──────────────────────────────────────────┐ | ||
| │ │ │ | ||
| │ For each section: │ | ||
| │ - Render content │ | ||
| │ - Track source tile coords │ | ||
| │ - Track template coords (if applicable) │ | ||
| └─────────────────┘ │ | ||
| │ │ | ||
| ▼ ▼ | ||
| ┌─────────────────┐ ┌───────────────────┐ | ||
| │ Raw Prompt │ │ Annotated Prompt │ | ||
| │ (sent to LLM) │ │ (for UI display) │ | ||
| └─────────────────┘ └───────────────────┘ | ||
| │ | ||
| ▼ | ||
| ┌───────────────────┐ | ||
| │ Chat UI │ | ||
| │ Prompt Viewer │ | ||
| └───────────────────┘ | ||
| ``` | ||
|
|
||
| ## Rendering Primitive Provenance | ||
|
|
||
| For code-based primitives (`GenericTile`, `Folder`, etc.), track which primitive rendered the content: | ||
|
|
||
| ```typescript | ||
| interface PromptSection { | ||
| source: { | ||
| tileCoords: string; | ||
| tileTitle: string; | ||
| templateCoords?: string; // If tile-based template | ||
| templateName?: string; | ||
| primitive?: string; // "GenericTile", "Folder", etc. | ||
| }; | ||
| } | ||
| ``` | ||
|
|
||
| This enables: | ||
| - "This was rendered by the GenericTile primitive with fields: title, content" | ||
| - Future: link to primitive documentation | ||
|
|
||
| ## Interactive Features | ||
|
|
||
| ### 1. Click to Navigate | ||
| Click any section → navigate to source tile in the map view. | ||
|
|
||
| ### 2. Edit in Place | ||
| "Edit this tile" button → opens tile editor, changes reflect in next prompt. | ||
|
|
||
| ### 3. Template Inspection | ||
| "View template" → shows the template tile that rendered this section. | ||
|
|
||
| ### 4. Diff View (Future) | ||
| Compare two prompt versions to see what changed after tile edits. | ||
|
|
||
| ## Migration Considerations | ||
|
|
||
| ### Backward Compatibility | ||
| - Existing `buildPrompt()` API continues to work (returns raw string) | ||
| - New `buildAnnotatedPrompt()` for provenance-aware callers | ||
| - Or: `buildPrompt()` returns `AnnotatedPrompt`, with `.raw` for the string | ||
|
|
||
| ### Chat History | ||
| - Existing messages don't have provenance data | ||
| - New messages store provenance | ||
| - UI gracefully handles missing provenance (shows raw text only) | ||
|
|
||
| ## Open Questions | ||
|
|
||
| 1. **Granularity:** How fine-grained should sections be? Per-tile? Per XML tag? Per line? | ||
|
|
||
| 2. **Performance:** Does tracking provenance add significant overhead? Benchmark needed. | ||
|
|
||
| 3. **Storage:** Store full annotated prompt, or reconstruct from tile versions on demand? | ||
|
|
||
| 4. **Nesting display:** How to show deeply nested template composition without overwhelming the UI? | ||
|
|
||
| ## Non-Goals (For This Feature) | ||
|
|
||
| - Real-time prompt preview as you edit tiles (separate feature) | ||
| - Prompt diffing between versions | ||
| - Template debugging/stepping | ||
| - Prompt optimization suggestions |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for AnnotatedPrompt and PromptSection implementations
echo "=== Searching for AnnotatedPrompt interface/type ==="
rg -n "interface AnnotatedPrompt|type AnnotatedPrompt" src/ --type ts
echo -e "\n=== Searching for PromptSection interface/type ==="
rg -n "interface PromptSection|type PromptSection" src/ --type ts
echo -e "\n=== Searching for buildAnnotatedPrompt function ==="
rg -n "function buildAnnotatedPrompt|buildAnnotatedPrompt.*=" src/ --type ts
echo -e "\n=== Searching for PromptViewer component ==="
fd "PromptViewer" src/ --type f
echo -e "\n=== Checking buildPrompt return type changes ==="
rg -n "buildPrompt.*AnnotatedPrompt" src/ --type tsRepository: Diplow/hexframe
Length of output: 312
🏁 Script executed:
#!/bin/bash
echo "=== Repository structure ==="
ls -la
echo -e "\n=== src/ directory ==="
ls -la src/ 2>&1 | head -20
echo -e "\n=== TypeScript files in repository ==="
fd --type f -e ts -e tsx | head -20Repository: Diplow/hexframe
Length of output: 4797
Move this design document to docs/features/planned/ to clarify it describes future work, not an implemented feature.
This comprehensive design document is not yet implemented. No traces of AnnotatedPrompt, PromptSection, buildAnnotatedPrompt(), or PromptViewer component exist in the codebase. Placing it in docs/features/ incorrectly suggests the feature is complete; it should be repositioned to a planned features directory to distinguish design proposals from shipped functionality.
🤖 Prompt for AI Agents
In @docs/features/PROMPT_PROVENANCE_UI.md around lines 1-294, The doc
incorrectly lives in the shipped/features area and implies implemented APIs;
move the design doc out of the public "features" listing into the
planned/features area (or otherwise mark it as "Planned") and update the
document text to clearly state it's a design proposal (not implemented). Remove
or clarify any language that presents APIs as existing (references to
AnnotatedPrompt, PromptSection, buildAnnotatedPrompt, PromptViewer) so it
doesn't read like shipped code, and if there is a FEATURES index or sidebar
update include this doc under "Planned" rather than active features.
| it('should allow built-in templates for any visibility', async () => { | ||
| // Built-in templates are always considered public/available | ||
| await expect(service.validateVisibility('system', 'public', 'public')) | ||
| .resolves.not.toThrow() | ||
| await expect(service.validateVisibility('user', 'public', 'public')) | ||
| .resolves.not.toThrow() | ||
| await expect(service.validateVisibility('organizational', 'public', 'public')) | ||
| .resolves.not.toThrow() | ||
| await expect(service.validateVisibility('context', 'public', 'public')) | ||
| .resolves.not.toThrow() | ||
| }) |
There was a problem hiding this comment.
Test does not fully verify "any visibility" claim.
The test description states "should allow built-in templates for any visibility" but only tests 'public', 'public' combinations. To fully verify the claim, also test private tiles with built-in templates.
🔎 Suggested fix
it('should allow built-in templates for any visibility', async () => {
// Built-in templates are always considered public/available
await expect(service.validateVisibility('system', 'public', 'public'))
.resolves.not.toThrow()
await expect(service.validateVisibility('user', 'public', 'public'))
.resolves.not.toThrow()
await expect(service.validateVisibility('organizational', 'public', 'public'))
.resolves.not.toThrow()
await expect(service.validateVisibility('context', 'public', 'public'))
.resolves.not.toThrow()
+ // Also verify built-in templates work with private tiles
+ await expect(service.validateVisibility('system', 'private', 'public'))
+ .resolves.not.toThrow()
+ await expect(service.validateVisibility('user', 'private', 'public'))
+ .resolves.not.toThrow()
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| it('should allow built-in templates for any visibility', async () => { | |
| // Built-in templates are always considered public/available | |
| await expect(service.validateVisibility('system', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| await expect(service.validateVisibility('user', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| await expect(service.validateVisibility('organizational', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| await expect(service.validateVisibility('context', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| }) | |
| it('should allow built-in templates for any visibility', async () => { | |
| // Built-in templates are always considered public/available | |
| await expect(service.validateVisibility('system', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| await expect(service.validateVisibility('user', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| await expect(service.validateVisibility('organizational', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| await expect(service.validateVisibility('context', 'public', 'public')) | |
| .resolves.not.toThrow() | |
| // Also verify built-in templates work with private tiles | |
| await expect(service.validateVisibility('system', 'private', 'public')) | |
| .resolves.not.toThrow() | |
| await expect(service.validateVisibility('user', 'private', 'public')) | |
| .resolves.not.toThrow() | |
| }) |
🤖 Prompt for AI Agents
In
@src/lib/domains/agentic/services/__tests__/template-allowlist.service.test.ts
around lines 270-280, The test claims "allow built-in templates for any
visibility" but only asserts public/public cases; update the test in
template-allowlist.service.test.ts (the it block that calls
service.validateVisibility) to also assert that built-in templates (e.g.,
template types like 'system', 'user', 'organizational', 'context') do not throw
when passed private visibility combinations (for example 'private' or other
non-public visibility strings used in your codebase) by adding
expect(...validateVisibility(..., 'private', ...)).resolves.not.toThrow() checks
alongside the existing public checks so both public and private visibility cases
are covered.
| ``` | ||
| templates/ | ||
| ├── index.ts # Public API exports | ||
| ├── _prompt-builder.ts # Core implementation (template lookup, rendering) | ||
| ├── _system-template.ts # SYSTEM tile template and data types | ||
| ├── _user-template.ts # USER tile template and data types | ||
| ├── _hexrun-orchestrator-template.ts # @-mention orchestration template | ||
| ├── _pre-processor/ # {{@Template}} tag expansion | ||
| │ ├── index.ts | ||
| │ ├── _parser.ts | ||
| │ └── _resolver.ts | ||
| ├── _templates/ # Rendering primitive functions | ||
| │ ├── index.ts # Template registry | ||
| │ ├── _generic-tile.ts # GenericTile() renderer | ||
| │ ├── _folder.ts # Folder() renderer | ||
| │ ├── _tile-or-folder.ts # TileOrFolder() renderer | ||
| │ └── _hexplan.ts # HexPlan() renderer | ||
| ├── _internals/ # Shared utilities | ||
| │ ├── types.ts # PromptData and related types | ||
| │ ├── utils.ts # XML escaping, content checks | ||
| │ └── section-builders.ts # Context/subtask/ancestor section builders | ||
| └── __tests__/ # Test files | ||
| ``` |
There was a problem hiding this comment.
Add language specifiers to fenced code blocks.
Lines 115 and 161 have fenced code blocks without language specifiers. For text diagrams and directory trees, use ```text or ```plaintext.
🔎 Proposed fix
Line 115:
-```
+```text
templates/
├── index.ts # Public API exportsLine 161:
-```
+```text
Template "my-agent-template" (parent)
├── [1] Sub-template "HeaderSection"Also applies to: 161-175
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
115-115: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
In @src/lib/domains/agentic/templates/README.md around lines 115-137, The fenced
code blocks showing the directory tree and template diagram in README.md (the
blocks beginning with the templates/ listing and the "Template
\"my-agent-template\" (parent)" section) lack language specifiers; update those
triple-backtick fences to use a plain text specifier such as ```text or
```plaintext to improve rendering and syntax highlighting for the diagram blocks
referenced in the README examples (the templates/ tree block and the Template
"my-agent-template" block).
…e column The templateName column was created with VARCHAR(255) and no unique constraint, but the spec in TEMPLATES_AS_TILES.md requires: - VARCHAR(100) length limit - UNIQUE constraint for global template name uniqueness Changes: - Add migration 0016 to add UNIQUE constraint and change column to VARCHAR(100) - Update schema definition to match (length: 100, uniqueIndex) This fixes 4 failing integration tests that validate these constraints. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add user_template_allowlist table for per-user allowed templates - Create DrizzleTemplateAllowlistRepository for database operations - Add tRPC endpoints: getEffectiveAllowlist, addToAllowlist, removeFromAllowlist - Update _TypeSelectorField to fetch allowed types from API dynamically - Change itemType from MapItemType enum to ItemTypeValue (MapItemType | string) - Update entire type chain: domain → infrastructure → services → API → cache - Add type assertions for enum comparisons to fix ESLint errors - Isolate flaky template-name-column tests in test runner - Fix migration to reference "users" table (not "user") 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/map/Cache/Lifecycle/MutationCoordinator/mutation-coordinator.ts (2)
1084-1111: HardcodedMapItemType.CONTEXTmay not preserve custom item types.
_createOptimisticItem(line 1106) hardcodesitemType: MapItemType.CONTEXTregardless of what type is passed tocreateItem. Since thecreateItemmethod now acceptsitemType: string, this should be passed through to the optimistic item.🔎 Suggested fix
private _createOptimisticItem( coordId: string, coords: ReturnType<typeof CoordSystem.parseId>, data: { title?: string; name?: string; description?: string; content?: string; preview?: string; link?: string; + itemType?: string; }, parentId: string | null ): MapItemAPIContract { return { id: `temp_${Date.now()}`, coordinates: coordId, title: data.title ?? "New Item", content: data.content ?? "", preview: data.preview, link: data.link ?? "", depth: coords.path.length, parentId, - itemType: MapItemType.CONTEXT, + itemType: data.itemType ?? MapItemType.CONTEXT, ownerId: this.config.mapContext?.userId ?? "unknown", originId: null, visibility: Visibility.PRIVATE, }; }
1254-1269:_reconstructApiDataloses actual item type instead of preserving it from tile data.Line 1264 hardcodes
itemType: MapItemType.CONTEXTinstead of usingtile.data.itemType. SinceTileData.data.itemTypeis populated during the adapt function and contains the actual item type, this causes item types to be lost during optimistic updates and rollbacks. The method already reads other fields fromtile.data(title, content, preview, link, visibility), so itemType should be preserved consistently.🔎 Suggested fix
private _reconstructApiData(tile: TileData): MapItemAPIContract { return { id: String(tile.metadata.dbId), coordinates: tile.metadata.coordId, depth: tile.metadata.depth, title: tile.data.title, content: tile.data.content, preview: tile.data.preview, link: tile.data.link, parentId: null, - itemType: MapItemType.CONTEXT, + itemType: tile.data.itemType ?? MapItemType.CONTEXT, ownerId: tile.metadata.ownerId ?? this.config.mapContext?.userId ?? "unknown", originId: null, visibility: tile.data.visibility ?? Visibility.PRIVATE, }; }
🤖 Fix all issues with AI Agents
In @drizzle/seeds/templates.seed.ts:
- Around line 239-266: The _createTemplate function performs two INSERTs (into
vde_base_items and vde_map_items) without a transaction, risking orphaned
vde_base_items if the second INSERT fails; modify _createTemplate to run both
INSERTs inside a single DB transaction (use your SQL client's transaction helper
or explicit BEGIN/COMMIT/ROLLBACK), so that on any error you ROLLBACK and
rethrow; ensure the transaction scope includes creating baseResult (INSERT INTO
vde_base_items) and the subsequent INSERT INTO vde_map_items, and handle errors
to always rollback before throwing.
- Around line 125-149: Both _ensureGrandparentExists and
_ensureTemplatesParentExists perform two separate INSERTs (see
baseResult/mapResult and the subsequent id checks) without transactional
protection, which can leave the DB in a partial state; refactor each function to
execute both INSERTs inside a single transaction (use your SQL client's
transaction API or sql.begin/transaction) so that you RETURN the map item id
only after committing and rollback on any error, and remove the separate
non-atomic insert pattern by using the transaction-scoped queries and error
handling.
In @src/lib/domains/agentic/templates/_hexrun-orchestrator-template.ts:
- Around line 109-110: Replace the string literal comparison in
shouldUseOrchestrator with the MapItemType enum: change the check from itemType
=== 'system' to itemType === MapItemType.SYSTEM, and add an import for
MapItemType from ~/lib/domains/mapping/_objects/map-item so the function uses
the enum for type safety and consistency with other templates.
In @src/server/api/routers/map/map-schemas.ts:
- Around line 57-60: itemTypeSchema currently only bans "user" and allows any
non-empty string; update itemTypeSchema to trim input, enforce a max length of
50, reject strings that become empty after trimming, restrict characters to only
alphanumeric, hyphen and underscore (e.g. /^[A-Za-z0-9_-]+$/), and keep the
case-insensitive "user" prohibition; implement this by replacing the existing
z.string().min(1).refine(...) chain with a pipeline that trims then checks
length <= 50, validates the regex, and refines against val.toLowerCase() ===
"user" (or equivalent) so all checks run in order.
🧹 Nitpick comments (12)
src/lib/domains/mapping/_objects/map-item.ts (1)
135-135: Consider extracting repeated type cast to a constant.The pattern
(MapItemType.USER as ItemTypeValue)appears twice in this file and potentially across other files. Consider extracting it to improve maintainability:const USER_ITEM_TYPE_VALUE: ItemTypeValue = MapItemType.USER as ItemTypeValue;Then use:
if (attrs.itemType === USER_ITEM_TYPE_VALUE)Also applies to: 178-178
src/lib/domains/mapping/types/parameters.ts (1)
19-26: Consider adding format validation and normalization for custom item types.While the case-insensitive "user" exclusion is good, the schema lacks:
- Format validation - could allow leading/trailing spaces, special characters
- Case normalization - "MyType" and "mytype" would be treated as different types
Consider:
const ItemTypeSchema = z.string() .min(1) .trim() .regex(/^[a-z][a-z0-9_-]*$/i, { message: "Item type must start with a letter and contain only letters, numbers, hyphens, or underscores" }) .transform(val => val.toLowerCase()) .refine( (val) => val !== "user", { message: "The 'user' item type is reserved for system-created root tiles" } );This ensures consistent matching and prevents problematic type names across the allowlist, templates, and tile creation flows.
src/server/api/routers/map/map-schemas.ts (1)
43-56: Consider documenting the reserved types more explicitly.The comment mentions
"user"is reserved but doesn't explain the full context of reserved vs. built-in vs. custom types. Based on the PR objectives mentioningRESERVED_ITEM_TYPES, consider enhancing the documentation to clarify:
- Built-in types:
"organizational","context","system"(from MapItemType enum)- Reserved types:
"user"(system-controlled, cannot be set via API)- Custom types: Any other valid string (e.g.,
"template")This would help API consumers understand the type hierarchy.
src/app/map/Chat/Timeline/Widgets/TileWidget/__tests__/type-selector.test.tsx (3)
6-28: Mock data doesn't reflect the custom type support added in this PR.The mock returns
allowedTypes: ['organizational', 'context', 'system'], but the PR introduces support for custom item types (e.g.,"template"). Consider adding test cases that verify:
- TypeSelector handles custom types from the allowlist
- The UI properly displays custom types alongside built-in types
- Validation works correctly when selecting custom types
🔎 Example test case for custom types
Add a test that mocks a custom type in the allowlist:
it("should display custom types from allowlist", async () => { // Mock with custom type vi.mocked(api.agentic.getEffectiveAllowlist.useQuery).mockReturnValue({ data: { allowedTypes: ['organizational', 'context', 'system', 'template'] }, isLoading: false, } as any); const { _TypeSelectorField } = await import( "~/app/map/Chat/Timeline/Widgets/TileWidget/_internals/form/_TypeSelectorField" ); render( <_TypeSelectorField value="template" onChange={vi.fn()} /> ); const select = screen.getByRole("combobox"); await userEvent.click(select); expect(screen.getByText("Template")).toBeInTheDocument(); });
170-170: Remove self-evident comment.This comment doesn't add meaningful information since the mock structure is already clear and used by previous tests without explanation.
- // Uses the mock already defined at the top of the file - it("should include TypeSelectorField in the form", async () => {
310-328: Test verifies initial state but not preservation across updates.The test name says "should preserve itemType when prop is user type" but only checks the initial state. Consider adding a follow-up test that verifies the user type cannot be changed through state updates (if that's the intended behavior).
Example additional test:
it("should maintain user type even when setItemType is attempted", async () => { const { renderHook, act } = await import("@testing-library/react"); const { useTileState } = await import( "~/app/map/Chat/Timeline/Widgets/TileWidget/useTileState" ); const { result } = renderHook(() => useTileState({ title: "Root Tile", preview: "", content: "", itemType: "user", tileId: "root", }) ); // Attempt to change type act(() => { result.current.editing.setItemType("context"); }); // Verify user type is protected (or allowed to change, depending on requirements) expect(result.current.editing.itemType).toBe(/* expected behavior */); });src/lib/domains/mapping/_repositories/map-item.ts (1)
257-269: User type restrictions are already enforced at the service layer; infrastructure layer could add defensive validation.The service layer validates before calling
batchUpdateItemTypeWithStructuralDescendants:
- Prevents setting items to USER type
- Prevents changing USER-typed root items
- Only calls batch for SYSTEM or CONTEXT types
Additionally, USER items are architecturally constrained to always be root items with no descendants (validated in
MapItemValidation.validateParentChildRelationship), so they cannot be updated by a method that only updates descendants. However, the infrastructure layer itself (write-queries.ts) lacks defensive validation. Consider adding a check to reject USER type at the query layer as well.src/server/db/schema/_tables/auth/user-template-allowlist.ts (1)
33-34: Remove redundant index on userId.The
uniqueIndexonuserId(line 33) already provides index functionality. The additional regularindexon the same column (line 34) is redundant since PostgreSQL automatically creates an index for unique constraints.🔎 Proposed fix to remove redundant index
}, (table) => ({ uniqueUserAllowlist: uniqueIndex("unique_user_allowlist").on(table.userId), - userIdIdx: index("user_template_allowlist_user_id_idx").on(table.userId), }));src/server/api/routers/agentic/agentic.ts (1)
607-608: Consider extracting repository/service instantiation to a higher scope.Each endpoint creates new
DrizzleTemplateAllowlistRepositoryandTemplateAllowlistServiceinstances. For better performance and consistency, consider:
- Creating the repository once in a middleware (similar to
mappingServiceMiddlewareandagenticServiceMiddleware)- Passing it through the tRPC context
- Reusing the same instance across all three endpoints
This reduces object creation overhead and aligns with the existing service injection pattern used elsewhere in this router.
💡 Example refactor pattern
Create a middleware similar to existing ones:
// In middleware or router setup const templateAllowlistMiddleware = t.procedure.use(({ ctx, next }) => { const repository = new DrizzleTemplateAllowlistRepository(ctx.db) const templateAllowlistService = new TemplateAllowlistService(repository) return next({ ctx: { ...ctx, templateAllowlistService } }) })Then use in endpoints:
getEffectiveAllowlist: protectedProcedure .use(templateAllowlistMiddleware) .query(async ({ ctx }) => { const allowlist = await ctx.templateAllowlistService.getEffectiveAllowlist( ctx.session?.userId ?? null ) return { allowedTypes: allowlist } })Also applies to: 620-621, 656-657
src/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.ts (1)
34-52: Consider using Drizzle's conflict handling for upsert.The current implementation calls
getUserAllowlist(line 35), which adds an extra database round-trip. Drizzle ORM supports PostgreSQL'sON CONFLICTfor atomic upserts:🔎 Proposed refactor using Drizzle's onConflictDoUpdate
async saveUserAllowlist(allowlist: UserAllowlist): Promise<void> { - const existing = await this.getUserAllowlist(allowlist.userId); - - if (existing) { - await this.db - .update(userTemplateAllowlist) - .set({ - allowedTemplates: allowlist.allowedTemplates, - updatedAt: new Date(), - }) - .where(eq(userTemplateAllowlist.userId, allowlist.userId)); - } else { - await this.db.insert(userTemplateAllowlist).values({ - id: nanoid(), - userId: allowlist.userId, - allowedTemplates: allowlist.allowedTemplates, - }); - } + await this.db + .insert(userTemplateAllowlist) + .values({ + id: nanoid(), + userId: allowlist.userId, + allowedTemplates: allowlist.allowedTemplates, + }) + .onConflictDoUpdate({ + target: userTemplateAllowlist.userId, + set: { + allowedTemplates: allowlist.allowedTemplates, + updatedAt: new Date(), + }, + }); }This reduces two queries to one atomic operation and handles race conditions better.
src/lib/domains/mapping/services/_item-services/_item-crud.service.ts (2)
563-592: Switch falls through silently for custom parent types.When
parentItemTypeis a custom type (e.g., "template"), none of the switch cases match, and the function exits without validation. This is likely correct behavior (custom parents don't constrain children), but consider adding an early return for clarity:🔎 Optional: Add early return for custom parent types
const parentItemType = parent.attrs.itemType; + // Custom parent types don't constrain child itemTypes + if (!isBuiltInItemType(parentItemType)) { + return; + } + // Rule: ORGANIZATIONAL tiles can only be under USER or ORGANIZATIONAL if (newItemType === (MapItemType.ORGANIZATIONAL as ItemTypeValue)) {
100-100: Consider a helper to reduce cast verbosity.Multiple lines use the
(MapItemType.X as ItemTypeValue)pattern. A simple helper could improve readability:🔎 Optional refactor
// At module level or in infrastructure const asItemType = (type: MapItemType): ItemTypeValue => type; // Usage if (itemType === asItemType(MapItemType.USER)) { ... }Also applies to: 195-195, 199-199, 207-207
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (44)
drizzle/migrations/0017_add_user_template_allowlist.sqldrizzle/seeds/templates.seed.tsscripts/run-tests.shsrc/app/map/Cache/Handlers/NavigationHandler/_helpers/_loading/region-loader.tssrc/app/map/Cache/Handlers/NavigationHandler/_helpers/_validation/tile-converter.tssrc/app/map/Cache/Lifecycle/MutationCoordinator/_mutation-wrappers.tssrc/app/map/Cache/Lifecycle/MutationCoordinator/mutation-coordinator.tssrc/app/map/Cache/Lifecycle/_callbacks/mutation-callbacks.tssrc/app/map/Cache/Services/types.tssrc/app/map/Cache/types/handlers.tssrc/app/map/Cache/types/index.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/TileForm.tsxsrc/app/map/Chat/Timeline/Widgets/TileWidget/__tests__/type-selector.test.tsxsrc/app/map/Chat/Timeline/Widgets/TileWidget/_internals/_handlers.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/_internals/form/_TypeSelectorField.tsxsrc/app/map/Chat/Timeline/Widgets/TileWidget/tile-widget.tsxsrc/app/map/Chat/Timeline/Widgets/TileWidget/useTileState.tsxsrc/app/map/Chat/Timeline/_components/_renderers/widget-renderers.tsxsrc/app/map/Chat/Timeline/_utils/creation-handlers.tssrc/app/map/Chat/Timeline/_utils/tile-handlers.tssrc/lib/domains/agentic/infrastructure/index.tssrc/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.tssrc/lib/domains/agentic/infrastructure/template-allowlist/index.tssrc/lib/domains/agentic/services/task-execution.service.tssrc/lib/domains/agentic/templates/_hexrun-orchestrator-template.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/templates/_prompt-builder.tssrc/lib/domains/mapping/_actions/_map-item-copy-helpers.tssrc/lib/domains/mapping/_actions/_map-item-creation-helpers.tssrc/lib/domains/mapping/_objects/index.tssrc/lib/domains/mapping/_objects/map-item.tssrc/lib/domains/mapping/_repositories/map-item.tssrc/lib/domains/mapping/infrastructure/index.tssrc/lib/domains/mapping/infrastructure/map-item/types.tssrc/lib/domains/mapping/services/_item-services/_item-crud.service.tssrc/lib/domains/mapping/types/item-attributes.tssrc/lib/domains/mapping/types/parameters.tssrc/lib/domains/mapping/utils/index.tssrc/server/api/routers/agentic/agentic.tssrc/server/api/routers/map/map-items.tssrc/server/api/routers/map/map-schemas.tssrc/server/db/schema/_tables/auth/user-template-allowlist.tssrc/server/db/schema/_tables/mapping/map-items.tssrc/server/db/schema/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/lib/domains/agentic/templates/_internals/types.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always use absolute imports with
~/prefix instead of relative imports (./or../), as enforced by ESLintno-restricted-importsrule
Files:
src/lib/domains/agentic/infrastructure/index.tssrc/lib/domains/agentic/services/task-execution.service.tssrc/lib/domains/agentic/infrastructure/template-allowlist/index.tssrc/lib/domains/mapping/utils/index.tssrc/server/api/routers/agentic/agentic.tssrc/lib/domains/mapping/types/item-attributes.tssrc/lib/domains/mapping/infrastructure/index.tssrc/server/db/schema/_tables/mapping/map-items.tssrc/lib/domains/mapping/_objects/index.tssrc/app/map/Cache/types/handlers.tssrc/server/db/schema/_tables/auth/user-template-allowlist.tssrc/app/map/Cache/types/index.tssrc/lib/domains/agentic/templates/_hexrun-orchestrator-template.tssrc/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.tssrc/server/db/schema/index.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/TileForm.tsxsrc/lib/domains/mapping/infrastructure/map-item/types.tssrc/app/map/Chat/Timeline/_utils/tile-handlers.tssrc/app/map/Chat/Timeline/_components/_renderers/widget-renderers.tsxsrc/server/api/routers/map/map-schemas.tssrc/lib/domains/mapping/_actions/_map-item-copy-helpers.tssrc/lib/domains/agentic/templates/_prompt-builder.tssrc/app/map/Chat/Timeline/_utils/creation-handlers.tssrc/lib/domains/mapping/_objects/map-item.tssrc/app/map/Cache/Lifecycle/_callbacks/mutation-callbacks.tssrc/app/map/Cache/Handlers/NavigationHandler/_helpers/_validation/tile-converter.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/useTileState.tsxsrc/lib/domains/mapping/_actions/_map-item-creation-helpers.tssrc/app/map/Cache/Services/types.tsdrizzle/seeds/templates.seed.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/tile-widget.tsxsrc/server/api/routers/map/map-items.tssrc/app/map/Cache/Handlers/NavigationHandler/_helpers/_loading/region-loader.tssrc/app/map/Cache/Lifecycle/MutationCoordinator/_mutation-wrappers.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/_internals/form/_TypeSelectorField.tsxsrc/app/map/Cache/Lifecycle/MutationCoordinator/mutation-coordinator.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/__tests__/type-selector.test.tsxsrc/lib/domains/mapping/services/_item-services/_item-crud.service.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/_internals/_handlers.tssrc/lib/domains/mapping/types/parameters.tssrc/lib/domains/mapping/_repositories/map-item.ts
src/lib/domains/agentic/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
buildPrompt()function fromsrc/lib/domains/agentic/services/prompt-executor.service.tsto deterministically generate XML prompts from tile coordinates
Files:
src/lib/domains/agentic/infrastructure/index.tssrc/lib/domains/agentic/services/task-execution.service.tssrc/lib/domains/agentic/infrastructure/template-allowlist/index.tssrc/lib/domains/agentic/templates/_hexrun-orchestrator-template.tssrc/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.tssrc/lib/domains/agentic/templates/_prompt-builder.ts
src/server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use tRPC for type-safe API in backend implementations
Files:
src/server/api/routers/agentic/agentic.tssrc/server/db/schema/_tables/mapping/map-items.tssrc/server/db/schema/_tables/auth/user-template-allowlist.tssrc/server/db/schema/index.tssrc/server/api/routers/map/map-schemas.tssrc/server/api/routers/map/map-items.ts
src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
src/app/**/*.{ts,tsx}: Use Next.js 15 App Router with progressive enhancement, following Static → Progressive → Dynamic component patterns
Use localStorage caching for performance optimization in frontend code
Files:
src/app/map/Cache/types/handlers.tssrc/app/map/Cache/types/index.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/TileForm.tsxsrc/app/map/Chat/Timeline/_utils/tile-handlers.tssrc/app/map/Chat/Timeline/_components/_renderers/widget-renderers.tsxsrc/app/map/Chat/Timeline/_utils/creation-handlers.tssrc/app/map/Cache/Lifecycle/_callbacks/mutation-callbacks.tssrc/app/map/Cache/Handlers/NavigationHandler/_helpers/_validation/tile-converter.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/useTileState.tsxsrc/app/map/Cache/Services/types.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/tile-widget.tsxsrc/app/map/Cache/Handlers/NavigationHandler/_helpers/_loading/region-loader.tssrc/app/map/Cache/Lifecycle/MutationCoordinator/_mutation-wrappers.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/_internals/form/_TypeSelectorField.tsxsrc/app/map/Cache/Lifecycle/MutationCoordinator/mutation-coordinator.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/__tests__/type-selector.test.tsxsrc/app/map/Chat/Timeline/Widgets/TileWidget/_internals/_handlers.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Vitest for tests (not Jest)
Files:
src/app/map/Chat/Timeline/Widgets/TileWidget/__tests__/type-selector.test.tsx
🧠 Learnings (7)
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Every tile has a semantic type (USER, ORGANIZATIONAL, CONTEXT, SYSTEM) that guides agent behavior; treat unclassified legacy tiles with null itemType as undefined
Applied to files:
src/lib/domains/agentic/services/task-execution.service.tssrc/lib/domains/mapping/types/item-attributes.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/TileForm.tsxsrc/app/map/Chat/Timeline/_utils/tile-handlers.tssrc/server/api/routers/map/map-schemas.tssrc/lib/domains/mapping/_objects/map-item.tssrc/app/map/Cache/Handlers/NavigationHandler/_helpers/_validation/tile-converter.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/useTileState.tsxsrc/app/map/Chat/Timeline/Widgets/TileWidget/tile-widget.tsxsrc/lib/domains/mapping/services/_item-services/_item-crud.service.tssrc/lib/domains/mapping/types/parameters.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to src/lib/domains/agentic/**/*.{ts,tsx} : Use `buildPrompt()` function from `src/lib/domains/agentic/services/prompt-executor.service.ts` to deterministically generate XML prompts from tile coordinates
Applied to files:
src/server/api/routers/agentic/agentic.tssrc/lib/domains/agentic/templates/_hexrun-orchestrator-template.tssrc/lib/domains/agentic/templates/_prompt-builder.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to **/*.{test,spec}.{js,jsx,ts,tsx} : Use Vitest for tests (not Jest)
Applied to files:
scripts/run-tests.shsrc/app/map/Chat/Timeline/Widgets/TileWidget/__tests__/type-selector.test.tsx
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to drizzle/migrations/**/*.ts : Use Drizzle ORM with PostgreSQL and store migrations in `/drizzle/migrations/`
Applied to files:
src/server/db/schema/_tables/mapping/map-items.tssrc/server/db/schema/_tables/auth/user-template-allowlist.tssrc/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.ts
📚 Learning: 2025-09-16T20:38:01.707Z
Learnt from: Diplow
Repo: Diplow/hexframe PR: 123
File: src/app/map/Chat/Timeline/Widgets/DebugLogsWidget.tsx:20-33
Timestamp: 2025-09-16T20:38:01.707Z
Learning: DebugLogsWidget in src/app/map/Chat/Timeline/Widgets/DebugLogsWidget.tsx must not use debugLogger for its own logging operations to avoid infinite loops, since render operations are logged by the debug system.
Applied to files:
src/app/map/Chat/Timeline/_components/_renderers/widget-renderers.tsx
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Use `updateItem` calls for agents to update hexplan tiles with status markers (🟡, ✅, 🔴)
Applied to files:
src/app/map/Chat/Timeline/_components/_renderers/widget-renderers.tsxsrc/lib/domains/mapping/services/_item-services/_item-crud.service.tssrc/lib/domains/mapping/_repositories/map-item.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to src/server/**/*.{ts,tsx} : Use tRPC for type-safe API in backend implementations
Applied to files:
src/server/api/routers/map/map-items.tssrc/app/map/Chat/Timeline/Widgets/TileWidget/_internals/form/_TypeSelectorField.tsx
🧬 Code graph analysis (18)
src/lib/domains/agentic/services/task-execution.service.ts (3)
src/lib/domains/mapping/_objects/index.ts (1)
ItemTypeValue(9-9)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (1)
ItemTypeValue(32-32)
src/server/api/routers/agentic/agentic.ts (4)
src/server/api/trpc.ts (2)
protectedProcedure(351-366)t(172-184)src/lib/domains/agentic/infrastructure/index.ts (1)
DrizzleTemplateAllowlistRepository(14-14)src/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.ts (1)
DrizzleTemplateAllowlistRepository(16-59)src/server/db/index.ts (1)
db(78-78)
src/app/map/Cache/types/handlers.ts (2)
src/lib/domains/mapping/types/item-attributes.ts (1)
MapItemCreateAttributes(45-51)src/app/map/Cache/Lifecycle/MutationCoordinator/mutation-coordinator.ts (1)
MutationResult(78-81)
src/server/db/schema/_tables/auth/user-template-allowlist.ts (1)
src/server/db/schema/_tables/auth/users.ts (1)
users(19-31)
src/lib/domains/agentic/templates/_hexrun-orchestrator-template.ts (5)
src/lib/domains/mapping/_objects/index.ts (1)
ItemTypeValue(9-9)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (1)
ItemTypeValue(32-32)src/lib/domains/agentic/templates/index.ts (1)
shouldUseOrchestrator(15-15)src/lib/domains/agentic/templates/_internals/utils.ts (1)
_hasContent(22-24)
src/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.ts (6)
src/server/db/index.ts (2)
schema(81-81)db(78-78)src/lib/domains/agentic/infrastructure/index.ts (1)
DrizzleTemplateAllowlistRepository(14-14)src/lib/domains/agentic/infrastructure/template-allowlist/index.ts (1)
DrizzleTemplateAllowlistRepository(1-1)src/server/db/schema/_tables/auth/user-template-allowlist.ts (1)
userTemplateAllowlist(20-35)src/lib/domains/mapping/_objects/index.ts (1)
Visibility(10-10)src/lib/domains/mapping/utils/index.ts (1)
Visibility(33-33)
src/lib/domains/mapping/infrastructure/map-item/types.ts (5)
src/lib/domains/mapping/_objects/index.ts (2)
ItemTypeValue(9-9)Visibility(10-10)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (2)
ItemTypeValue(32-32)Visibility(33-33)src/lib/domains/agentic/services/_templates/template-allowlist.service.ts (1)
Visibility(30-30)src/lib/domains/mapping/types/contracts.ts (1)
Visibility(30-30)
src/lib/domains/mapping/_actions/_map-item-copy-helpers.ts (3)
src/lib/domains/mapping/_objects/index.ts (1)
ItemTypeValue(9-9)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (1)
ItemTypeValue(32-32)
src/lib/domains/mapping/_objects/map-item.ts (6)
src/lib/domains/mapping/_objects/index.ts (5)
ItemTypeValue(9-9)MapItemType(6-6)Attrs(3-3)Attrs(18-18)Visibility(10-10)src/lib/domains/mapping/utils/index.ts (4)
ItemTypeValue(32-32)MapItemType(29-29)Coord(9-9)Visibility(33-33)src/lib/domains/mapping/types/contracts.ts (2)
MapItemType(30-30)Visibility(30-30)src/lib/domains/mapping/_objects/base-item.ts (1)
Attrs(8-8)src/lib/domains/mapping/utils/hex-coordinates.ts (1)
Coord(22-29)src/lib/domains/agentic/services/_templates/template-allowlist.service.ts (1)
Visibility(30-30)
src/app/map/Cache/Handlers/NavigationHandler/_helpers/_validation/tile-converter.ts (3)
src/lib/domains/mapping/_objects/index.ts (1)
ItemTypeValue(9-9)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (1)
ItemTypeValue(32-32)
src/lib/domains/mapping/_actions/_map-item-creation-helpers.ts (5)
src/lib/domains/mapping/_objects/index.ts (3)
ItemTypeValue(9-9)MapItemWithId(13-13)MapItemType(6-6)src/lib/domains/mapping/_objects/map-item.ts (2)
ItemTypeValue(69-69)MapItemWithId(183-183)src/lib/domains/mapping/utils/index.ts (3)
ItemTypeValue(32-32)Coord(9-9)MapItemType(29-29)src/lib/domains/mapping/utils/hex-coordinates.ts (1)
Coord(22-29)src/lib/domains/mapping/types/contracts.ts (1)
MapItemType(30-30)
src/app/map/Cache/Services/types.ts (3)
src/lib/domains/mapping/_objects/index.ts (1)
ItemTypeValue(9-9)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (1)
ItemTypeValue(32-32)
drizzle/seeds/templates.seed.ts (2)
src/lib/domains/agentic/templates/_system-template.ts (1)
SYSTEM_TEMPLATE(37-58)src/lib/domains/agentic/templates/_user-template.ts (1)
USER_TEMPLATE(59-91)
src/app/map/Chat/Timeline/Widgets/TileWidget/_internals/form/_TypeSelectorField.tsx (1)
src/commons/trpc/react.tsx (1)
api(74-74)
src/app/map/Cache/Lifecycle/MutationCoordinator/mutation-coordinator.ts (2)
src/lib/domains/mapping/types/item-attributes.ts (1)
MapItemCreateAttributes(45-51)src/app/map/Cache/types/handlers.ts (1)
MutationResult(62-67)
src/lib/domains/mapping/services/_item-services/_item-crud.service.ts (5)
src/lib/domains/mapping/_objects/index.ts (2)
ItemTypeValue(9-9)MapItemType(6-6)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (2)
ItemTypeValue(32-32)MapItemType(29-29)src/lib/domains/mapping/types/contracts.ts (1)
MapItemType(30-30)src/lib/domains/mapping/infrastructure/index.ts (1)
isBuiltInItemType(18-18)
src/lib/domains/mapping/types/parameters.ts (5)
src/lib/domains/mapping/_objects/index.ts (1)
MapItemType(6-6)src/lib/domains/mapping/utils/index.ts (1)
MapItemType(29-29)src/lib/domains/mapping/services/__tests__/helpers/_test-utilities.ts (1)
MapItemType(13-13)src/lib/domains/mapping/types/contracts.ts (1)
MapItemType(30-30)src/lib/domains/mapping/types/index.ts (2)
CreateMapItemParamsSchema(30-30)UpdateMapItemAttrsSchema(31-31)
src/lib/domains/mapping/_repositories/map-item.ts (3)
src/lib/domains/mapping/_objects/index.ts (2)
ItemTypeValue(9-9)MapItemWithId(13-13)src/lib/domains/mapping/_objects/map-item.ts (2)
ItemTypeValue(69-69)MapItemWithId(183-183)src/lib/domains/mapping/utils/index.ts (1)
ItemTypeValue(32-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build
- GitHub Check: Tests - Phase 1 (Main Suite)
- GitHub Check: Tests - Phase 2 (React Components)
🔇 Additional comments (55)
src/app/map/Chat/Timeline/Widgets/TileWidget/_internals/_handlers.ts (1)
16-29: No issues found. The type change fromEditableItemTypetostring | undefinedis correctly implemented with proper validation layering:
Type validation exists in services:
ItemCrudService(line 479) andPromptBuilder(line 49) useisBuiltInItemType()to enforce constraints. Built-in types and reserved types ("user") are protected at the service layer where domain logic resides.Handler design is correct:
_handleSave()is properly designed as a thin delegation layer. It passeseditItemTypeto the callback without validation—validation belongs in the service layer, not the UI handler.All callers updated:
tile-widget.tsx(line 126) correctly invokes_handleSave()with the new signature. TheWidgetHandlersinterface (widget-renderers.tsx line 16) and callback contracts are aligned.Type change is intentional: Supporting
string | undefinedenables custom item types ("template", "project", "workflow") as per PR objectives while maintaining type safety through service-layer validation.scripts/run-tests.sh (1)
87-87: LGTM: Appropriate test isolation for database integration test.Moving the template-name-column integration test to Phase 2 ensures proper isolation with single-fork execution, which is consistent with other database schema tests.
Also applies to: 141-142
src/lib/domains/mapping/_objects/map-item.ts (2)
64-69: LGTM: Well-documented type extension for custom item types.The
ItemTypeValuetype union cleanly extends the built-inMapItemTypeenum to support custom strings, enabling template-based types while preserving system semantics for the "user" type.
75-77: LGTM: Interface changes support template provenance tracking.The changes to accept
ItemTypeValueforitemTypeand the addition of optionaltemplateNameproperly support the Templates as Tiles feature for tracking tile origins.src/lib/domains/mapping/_actions/_map-item-creation-helpers.ts (1)
12-12: LGTM: Consistent adoption of ItemTypeValue throughout the helper.All method signatures properly updated to use
ItemTypeValue, maintaining consistency with the domain object changes. The type safety is preserved while enabling custom item types.Also applies to: 34-34, 61-61, 78-78, 82-82, 110-110
src/lib/domains/mapping/types/parameters.ts (1)
28-35: LGTM: Clean schema composition with backward compatibility.The union approach in
CreateItemTypeSchemamaintains backward compatibility with theMapItemTypeenum while supporting custom types. The consistent application across create and update schemas is well-structured.Also applies to: 43-43, 58-68
src/server/db/schema/index.ts (1)
25-25: LGTM: Consistent barrel export for new auth table.The export for the user-template-allowlist table follows the established pattern and is properly placed with other auth table exports.
src/server/db/schema/_tables/mapping/map-items.ts (3)
45-46: LGTM! Item type constraint removal aligns with PR objectives.The removal of
.$type<MapItemType>()correctly enables the item type system extension to accept custom string values (ItemTypeValue), as specified in the PR objectives. The existing check constraints (lines 67-74) continue to work with string-based comparisons.
53-53: LGTM! Template name column addition is correct.The new
templateNamecolumn withvarchar(100)nullable constraint aligns with migration 0015_add_template_name_column.sql mentioned in the PR objectives and enables template provenance tracking for tile-based templates.
88-90: Global uniqueness of template names is intentional and tested.The unique index on
templateNameis correctly implemented as global. Test coverage insrc/server/db/schema/__tests__/template-name-column.integration.test.tsexplicitly verifies this behavior:
- Line 17: Requirements document that "templateName has a unique constraint (globally unique)"
- Lines 162-202: Test explicitly verifies that the same
templateNameis rejected across different usersThe constraint is part of the "templates as tiles" feature design, where template names must be globally unique for consistent prompt rendering across the system.
src/lib/domains/agentic/infrastructure/index.ts (1)
11-14: LGTM! Clean addition of template allowlist repository export.The new
DrizzleTemplateAllowlistRepositoryexport follows the project's absolute import convention and correctly exposes the repository implementation for the template allowlist feature introduced in PR objective #6.src/lib/domains/mapping/_objects/index.ts (1)
9-9: LGTM! ItemTypeValue type export enables flexible item typing.The addition of
ItemTypeValueto the public exports correctly exposes the new flexible item type alias (MapItemType | string) introduced in PR objective #1, enabling consumers to work with both built-in and custom item types.src/lib/domains/mapping/infrastructure/index.ts (1)
15-18: LGTM! Item type utility functions correctly exposed.The addition of
isBuiltInItemType,isReservedItemType, andisCustomItemTypepredicates correctly exposes the type guard functions introduced in PR objective #1, enabling consumers to distinguish between built-in, reserved, and custom item types.src/lib/domains/agentic/infrastructure/template-allowlist/index.ts (1)
1-1: LGTM! Clean barrel export for template allowlist infrastructure.The new barrel export file follows the project's infrastructure organization pattern and correctly re-exports
DrizzleTemplateAllowlistRepositoryusing absolute imports, enabling consolidated access to the template allowlist repository implementation.src/app/map/Chat/Timeline/_components/_renderers/widget-renderers.tsx (1)
6-6: LGTM: Interface types updated consistently.The
WidgetHandlersinterface correctly updates thehandleTileSavesignature to acceptitemType?: string, maintaining consistency with the implementation intile-handlers.ts.Also applies to: 16-16
src/lib/domains/mapping/utils/index.ts (1)
32-32: LGTM: Public API type export added.The
ItemTypeValuetype is correctly re-exported to provide public access to the unified type system (MapItemType | string) that supports both built-in and custom item types.src/app/map/Cache/Handlers/NavigationHandler/_helpers/_validation/tile-converter.ts (1)
4-4: LGTM: Type widening maintains compatibility.The change from
MapItemTypetoItemTypeValuecorrectly widens the accepted types to support both built-in enum values and custom string types. SinceItemTypeValue = MapItemType | string, this maintains full backwards compatibility while enabling the new flexible item type system.Also applies to: 20-20, 33-33
src/app/map/Chat/Timeline/_utils/tile-handlers.ts (1)
4-4: Backend accepts string-based itemType values—type migration is correct.The changes appropriately widen
itemTypefromNonUserMapItemTypeStringtostring, aligning with theItemTypeValuesystem that supports custom template types. Backend mutation operations (createItemaccepts{ itemType: string };updateItemusesMapItemUpdateAttributes) confirm string-based values are supported. The optional parameter maintains backwards compatibility.src/app/map/Cache/Lifecycle/_callbacks/mutation-callbacks.ts (1)
26-26: Backend correctly accepts string-based itemType.Verification confirms the change is properly implemented. The MutationOperations interface and server router both accept
itemTypeas a string directly. The server-sidetoItemTypeValue()function is a pass-through, confirming the backend requires no enum transformation. The asymmetry noted (retainingtoVisibilityEnumwhile removingtoItemTypeEnum) reflects the actual API contract where visibility requires enum conversion but itemType does not.src/lib/domains/agentic/services/task-execution.service.ts (2)
20-20: LGTM: Type contract upgraded to support custom item types.The import change from
MapItemTypetoItemTypeValuealigns with the broader refactor to support both built-in and custom template types while maintaining type-only imports (no runtime dependency on mapping domain).
30-42: LGTM: TaskTile interface updated consistently.The
itemTypefield correctly uses the newItemTypeValuetype, maintaining compatibility withMapItemContractwhile supporting both built-in and custom item types.src/app/map/Chat/Timeline/Widgets/TileWidget/TileForm.tsx (1)
16-28: LGTM: Props updated to accept string-based item types.The change from
EditableItemTypetostringfor bothitemTypeandonItemTypeChangealigns with the broader migration to a flexible type system that supports custom templates alongside built-in types.src/app/map/Cache/types/index.ts (1)
90-107: LGTM: Mutation operation types updated to accept flexible item types.The change from
NonUserMapItemTypeStringtostringforitemTypein bothcreateItemOptimisticandupdateItemOptimisticoperations correctly aligns with the new type system that supports custom templates.src/lib/domains/mapping/types/item-attributes.ts (1)
27-40: LGTM: Well-documented type migration to support custom item types.The updated documentation clearly explains the distinction between built-in types, custom types, and the reserved "user" type. The change from
NonUserMapItemTypeStringtostringproperly supports the new flexible type system.drizzle/migrations/0017_add_user_template_allowlist.sql (2)
9-19: LGTM: Well-structured idempotent migration.The migration follows best practices:
- Idempotent statements (
IF NOT EXISTS) allow safe re-execution- Proper constraints (primary key, foreign key with cascade, unique constraint)
- Performance optimization via index on
user_id- Appropriate timestamps for audit trail
The
TEXT[]array type and'{}'default are correct PostgreSQL syntax.
9-16: The migration dependency is correctly ordered. Theuserstable is created in migration 0000_sweet_firebird.sql, which executes before migration 0017_add_user_template_allowlist.sql. The foreign key constraint will not cause any dependency errors.src/app/map/Cache/Handlers/NavigationHandler/_helpers/_loading/region-loader.ts (1)
5-5: Type migration is consistent and safe for read operations.The change from
MapItemTypetoItemTypeValueis appropriate here since this function loads existing items that have already been validated at creation time. The broader type accommodates both built-in and custom item types introduced in this PR.Also applies to: 22-22
src/app/map/Cache/Lifecycle/MutationCoordinator/_mutation-wrappers.ts (1)
1-1: Type safety reduction is properly mitigated by API-layer validation and integration tests.The change from
NonUserMapItemTypeStringtostringremoves compile-time guarantees, but this is acceptable given:
- API validation:
map-schemas.ts(lines 57-60) validates thatitemTypecannot be"user"via Zod schema with case-insensitive check and clear error message.- Integration test coverage: Tests in
map-items-itemtype.integration.test.ts(lines 156–168) explicitly verify that attempting to changeitemTypeto or from"user"is rejected.The runtime protection is solid and the risk is well-managed. No further action required.
src/lib/domains/mapping/_repositories/map-item.ts (1)
234-242: Type signature mismatch: interface acceptsItemTypeValuebut implementation expectsMapItemType.The repository interface signature changed to accept
ItemTypeValue(which includes all strings), while the implementation indb.tsstill usesMapItemType. The "Cannot change to/from USER type" restriction is already validated at the service layer in_item-crud.service.ts(lines 195 and 199) before the repository method is called, so the business rule is enforced despite the broader type signature. However, the type mismatch should be resolved—either update the implementation signature to match the interface, or update the interface to match the implementation.src/app/map/Chat/Timeline/_utils/creation-handlers.ts (1)
12-12: LGTM! Consistent type broadening for custom template support.The
itemTypeparameter correctly broadens tostringwhile preserving the default value "context". This aligns with the PR's goal of supporting custom template types through the tile system.Also applies to: 26-26
src/lib/domains/mapping/infrastructure/map-item/types.ts (2)
1-1: LGTM! Core type infrastructure correctly updated.The transition from
MapItemTypetoItemTypeValueis consistent across all DB attribute types (DbMapItemSelect,CreateMapItemDbAttrs,UpdateMapItemDbAttrs), properly supporting both built-in and custom item types.Also applies to: 10-10, 36-36, 47-47
13-13: The database migration fortemplateNamecolumn exists and is properly implemented.The migration
0015_add_template_name_column.sqladds thetemplate_namecolumn to vde_map_items as expected. A follow-up migration (0016) adds a UNIQUE constraint and adjusts the column length to VARCHAR(100). The type definitions correctly reflect the database schema with appropriate nullability across all three types (DbMapItemSelect, CreateMapItemDbAttrs, and UpdateMapItemDbAttrs).src/server/db/schema/_tables/auth/user-template-allowlist.ts (2)
37-38: LGTM! Type exports follow Drizzle ORM conventions.The type exports correctly use Drizzle's type inference for select and insert operations.
21-21: ID generation is properly implemented in the repository layer.The
idfield correctly has no database default. TheDrizzleTemplateAllowlistRepositorygenerates IDs usingnanoid()on line 47 of the insert operation before persisting to the database.src/app/map/Chat/Timeline/Widgets/TileWidget/tile-widget.tsx (1)
33-33: LGTM! Widget properly supports flexible item types.The
itemTypeprop andonSavecallback correctly acceptstring | nullto support both built-in and custom template types. The component logic appropriately handles null values throughout.Also applies to: 48-48
src/app/map/Cache/types/handlers.ts (1)
95-95: Validate itemType values at runtime.The type was broadened from
NonUserMapItemTypeStringtostring, removing compile-time type constraints. Ensure downstream validation exists in the mutation coordinator or repository layer to enforce:
- Reserved type restrictions
- Allowlist of valid itemType values
- Security rules preventing injection of arbitrary types
Without runtime validation, invalid or malicious itemType values could bypass security checks.
src/lib/domains/mapping/_actions/_map-item-copy-helpers.ts (1)
5-5: LGTM! Type migration to ItemTypeValue is correct.The type updates correctly propagate
ItemTypeValue(which isMapItemType | string) through the copy helper functions, maintaining type safety while supporting the extended item-type system.Also applies to: 50-50, 142-142, 151-151
src/server/api/routers/agentic/agentic.ts (3)
603-614: LGTM! Endpoint correctly retrieves effective allowlist.The implementation properly combines built-in templates with user-specific allowed templates using the service layer.
652-684: LGTM! Removal logic correctly prevents removing built-in templates.The implementation properly validates that built-in templates cannot be removed (line 665-667) and performs case-insensitive filtering (line 672-674).
616-650: Case-insensitive duplicate prevention is correctly implemented.The logic properly handles the flow: filters out built-in templates from the current allowlist, performs case-insensitive duplicate checking, and saves the new template with its original casing. Comprehensive test coverage exists in
template-allowlist.service.test.ts(lines 333-354), including case-insensitive matching scenarios. The implementation is sound and no changes are needed.src/lib/domains/agentic/infrastructure/template-allowlist/drizzle-allowlist-repository.ts (2)
19-32: LGTM! Clean implementation of getUserAllowlist.The method correctly queries the database and handles the null case when no allowlist exists for the user.
54-58: Placeholder implementation noted.The comment clearly indicates this is a placeholder that will look up template tile visibility in the future. This is acceptable for the current PR scope.
src/app/map/Cache/Services/types.ts (1)
2-2: LGTM! Type definitions correctly updated to ItemTypeValue.All
ServerServicemethod return types have been consistently updated to useItemTypeValueinstead ofMapItemType, maintaining type safety across the cache service layer.Also applies to: 19-19, 35-35, 49-49, 63-63, 77-77, 94-94
src/app/map/Chat/Timeline/Widgets/TileWidget/useTileState.tsx (1)
9-9: LGTM! Type widening aligns with ItemTypeValue migration.The change from strict union type to
string | nullis consistent with the broader PR's migration toItemTypeValue. The default fallback to'context'appropriately handles legacy tiles with null itemType, as noted in the learnings.Also applies to: 31-33, 64-64
drizzle/seeds/templates.seed.ts (1)
68-81: Well-structured template specifications.The
BUILTIN_TEMPLATE_SPECSarray cleanly defines the built-in templates with proper coordinates. The idempotent upsert logic in_seedTemplatehandles both creation and updates correctly.src/app/map/Cache/Lifecycle/MutationCoordinator/mutation-coordinator.ts (1)
30-30: Type signature updates are consistent.The widening of
itemTypefromNonUserMapItemTypeStringtostringin the config (line 30),createItem(line 292), and_prepareOptimisticUpdate(line 1180) aligns with the PR's ItemTypeValue migration.Also applies to: 292-292, 1180-1180
src/app/map/Chat/Timeline/Widgets/TileWidget/_internals/form/_TypeSelectorField.tsx (2)
35-43: Good defensive handling of allowlist data.The fallback to default types (line 38) and ensuring the current value is always displayed (lines 42-43) provide good UX resilience. This handles edge cases where the user has a custom type that's no longer in their allowlist.
21-28: Clean label formatting helper.
_formatTypeLabelcorrectly prioritizes built-in labels and falls back to capitalization for custom types.src/lib/domains/agentic/templates/_prompt-builder.ts (2)
42-71: Custom type handling looks correct but exhaustive check is now unreachable.The early return for non-built-in types (lines 49-51) means the
defaultcase withexhaustiveCheck: never(lines 66-69) is now unreachable for custom types. This is fine sinceisBuiltInItemTypeguarantees onlyMapItemTypevalues reach the switch, but consider adding a comment for clarity.🔎 Optional: Add clarifying comment
+ // At this point, itemType is guaranteed to be a built-in MapItemType + // due to the isBuiltInItemType check above switch (itemType) { case MapItemType.SYSTEM:
187-189: Type cast is appropriate for template data selection.The cast
(MapItemType.USER as ItemTypeValue)is necessary becausedata.itemTypecould be a custom string. Only built-in USER tiles get the user template data; all others (including custom types) get system template data.src/server/api/routers/map/map-items.ts (3)
171-191: Update handling is correct with optional itemType.The
updateParamstype (line 178) correctly marksitemTypeas optional, and line 191 only sets it when explicitly provided in the input.
30-37: Schema validation for reserved "user" type is correctly implemented.The
itemTypeSchemaat lines 57–60 of map-schemas.ts usesrefine()to reject the "user" type (case-insensitive), and bothitemCreationSchemaanditemUpdateSchemaapply this validation. ThetoItemTypeValue()pass-through function is appropriate here because tRPC enforces schema validation before the function executes, providing proper separation of concerns.
134-134: The code is correct as written.itemTypeis required initemCreationSchema(line 77 of map-schemas.ts shows no.optional()modifier), soinput.itemTypeis guaranteed to be a non-undefined string when passed totoItemTypeValue(). Zod validation ensures the schema constraint is enforced before the code reaches line 134.src/lib/domains/mapping/services/_item-services/_item-crud.service.ts (2)
477-481: Good: Custom types skip hierarchy validation.The early return for non-built-in types (lines 479-481) correctly allows custom types like "template" to bypass the strict SYSTEM/CONTEXT/ORGANIZATIONAL hierarchy rules.
61-61: Type signature updates are consistent with PR migration.The
ItemTypeValuetype inaddItemToMap(line 61) andupdateItem(line 171) properly supports both built-in and custom item types while maintaining the USER type restriction.Also applies to: 171-171
| const baseResult = await sql<Array<{ id: number }>>` | ||
| INSERT INTO vde_base_items (title, content, created_at, updated_at) | ||
| VALUES ('System', 'System-level organizational tiles', NOW(), NOW()) | ||
| RETURNING id | ||
| ` | ||
| const baseItemId = baseResult[0]?.id | ||
| if (!baseItemId) throw new Error('Failed to create grandparent base item') | ||
|
|
||
| const mapResult = await sql<Array<{ id: number }>>` | ||
| INSERT INTO vde_map_items ( | ||
| coord_user_id, coord_group_id, path, item_type, visibility, | ||
| parent_id, ref_item_id, created_at, updated_at | ||
| ) | ||
| VALUES ( | ||
| ${SYSTEM_USER_ID}, 0, ${grandparentPath}, 'organizational', 'public', | ||
| ${rootId}, ${baseItemId}, NOW(), NOW() | ||
| ) | ||
| RETURNING id | ||
| ` | ||
|
|
||
| const grandparentId = mapResult[0]?.id | ||
| if (!grandparentId) throw new Error('Failed to create grandparent map item') | ||
|
|
||
| console.log(` Created grandparent organizational tile at path: ${grandparentPath}`) | ||
| return grandparentId |
There was a problem hiding this comment.
Same atomicity issue in hierarchy tile creation.
Both _ensureGrandparentExists (lines 125-143) and _ensureTemplatesParentExists (lines 175-193) have the same two-phase insert pattern without transaction wrapping. Consider wrapping all creation functions in transactions for consistency.
🔎 Example fix pattern
+ await sql.begin(async (tx) => {
- const baseResult = await sql<Array<{ id: number }>>`
+ const baseResult = await tx<Array<{ id: number }>>`
INSERT INTO vde_base_items (title, content, created_at, updated_at)
VALUES ('System', 'System-level organizational tiles', NOW(), NOW())
RETURNING id
`
// ... validation ...
- const mapResult = await sql<Array<{ id: number }>>`
+ const mapResult = await tx<Array<{ id: number }>>`
INSERT INTO vde_map_items (...)
VALUES (...)
RETURNING id
`
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const baseResult = await sql<Array<{ id: number }>>` | |
| INSERT INTO vde_base_items (title, content, created_at, updated_at) | |
| VALUES ('System', 'System-level organizational tiles', NOW(), NOW()) | |
| RETURNING id | |
| ` | |
| const baseItemId = baseResult[0]?.id | |
| if (!baseItemId) throw new Error('Failed to create grandparent base item') | |
| const mapResult = await sql<Array<{ id: number }>>` | |
| INSERT INTO vde_map_items ( | |
| coord_user_id, coord_group_id, path, item_type, visibility, | |
| parent_id, ref_item_id, created_at, updated_at | |
| ) | |
| VALUES ( | |
| ${SYSTEM_USER_ID}, 0, ${grandparentPath}, 'organizational', 'public', | |
| ${rootId}, ${baseItemId}, NOW(), NOW() | |
| ) | |
| RETURNING id | |
| ` | |
| const grandparentId = mapResult[0]?.id | |
| if (!grandparentId) throw new Error('Failed to create grandparent map item') | |
| console.log(` Created grandparent organizational tile at path: ${grandparentPath}`) | |
| return grandparentId | |
| await sql.begin(async (tx) => { | |
| const baseResult = await tx<Array<{ id: number }>>` | |
| INSERT INTO vde_base_items (title, content, created_at, updated_at) | |
| VALUES ('System', 'System-level organizational tiles', NOW(), NOW()) | |
| RETURNING id | |
| ` | |
| const baseItemId = baseResult[0]?.id | |
| if (!baseItemId) throw new Error('Failed to create grandparent base item') | |
| const mapResult = await tx<Array<{ id: number }>>` | |
| INSERT INTO vde_map_items ( | |
| coord_user_id, coord_group_id, path, item_type, visibility, | |
| parent_id, ref_item_id, created_at, updated_at | |
| ) | |
| VALUES ( | |
| ${SYSTEM_USER_ID}, 0, ${grandparentPath}, 'organizational', 'public', | |
| ${rootId}, ${baseItemId}, NOW(), NOW() | |
| ) | |
| RETURNING id | |
| ` | |
| const grandparentId = mapResult[0]?.id | |
| if (!grandparentId) throw new Error('Failed to create grandparent map item') | |
| console.log(` Created grandparent organizational tile at path: ${grandparentPath}`) | |
| return grandparentId | |
| }) |
🤖 Prompt for AI Agents
In @drizzle/seeds/templates.seed.ts around lines 125 - 149, Both
_ensureGrandparentExists and _ensureTemplatesParentExists perform two separate
INSERTs (see baseResult/mapResult and the subsequent id checks) without
transactional protection, which can leave the DB in a partial state; refactor
each function to execute both INSERTs inside a single transaction (use your SQL
client's transaction API or sql.begin/transaction) so that you RETURN the map
item id only after committing and rollback on any error, and remove the separate
non-atomic insert pattern by using the transaction-scoped queries and error
handling.
| async function _createTemplate( | ||
| spec: BuiltinTemplateSpec, | ||
| parentId: number | ||
| ): Promise<void> { | ||
| const pathString = [...TEMPLATES_PARENT_PATH, spec.direction].join(',') | ||
|
|
||
| const baseResult = await sql<Array<{ id: number }>>` | ||
| INSERT INTO vde_base_items (title, content, created_at, updated_at) | ||
| VALUES (${spec.title}, ${spec.content}, NOW(), NOW()) | ||
| RETURNING id | ||
| ` | ||
|
|
||
| const baseItemId = baseResult[0]?.id | ||
| if (baseItemId === undefined) { | ||
| throw new Error(`Failed to create base item for template: ${spec.templateName}`) | ||
| } | ||
|
|
||
| await sql` | ||
| INSERT INTO vde_map_items ( | ||
| coord_user_id, coord_group_id, path, item_type, visibility, | ||
| parent_id, ref_item_id, template_name, created_at, updated_at | ||
| ) | ||
| VALUES ( | ||
| ${SYSTEM_USER_ID}, 0, ${pathString}, ${TEMPLATE_ITEM_TYPE}, 'public', | ||
| ${parentId}, ${baseItemId}, ${spec.templateName}, NOW(), NOW() | ||
| ) | ||
| ` | ||
| } |
There was a problem hiding this comment.
Missing transaction for atomic template creation.
This issue was flagged in a previous review and remains unaddressed. The _createTemplate function executes two separate INSERT statements without transaction wrapping. If the second insert fails, an orphan vde_base_items record will remain.
🤖 Prompt for AI Agents
In @drizzle/seeds/templates.seed.ts around lines 239 - 266, The _createTemplate
function performs two INSERTs (into vde_base_items and vde_map_items) without a
transaction, risking orphaned vde_base_items if the second INSERT fails; modify
_createTemplate to run both INSERTs inside a single DB transaction (use your SQL
client's transaction helper or explicit BEGIN/COMMIT/ROLLBACK), so that on any
error you ROLLBACK and rethrow; ensure the transaction scope includes creating
baseResult (INSERT INTO vde_base_items) and the subsequent INSERT INTO
vde_map_items, and handle errors to always rollback before throwing.
| export function shouldUseOrchestrator(itemType: ItemTypeValue, userMessage: string | undefined): boolean { | ||
| return itemType === 'system' && _hasContent(userMessage) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find MapItemType enum definition
echo "=== Searching for MapItemType enum definition ==="
rg -n "enum MapItemType|type MapItemType" --type=ts -A 10
echo ""
echo "=== Searching for MapItemType.SYSTEM usages and 'system' literals ==="
rg -n "MapItemType\.SYSTEM|itemType.*['\"]system['\"]" --type=ts -B 2 -A 2
echo ""
echo "=== Searching for ItemTypeValue type definition ==="
rg -n "type ItemTypeValue|interface ItemTypeValue" --type=ts -A 5Repository: Diplow/hexframe
Length of output: 50371
🏁 Script executed:
#!/bin/bash
# Examine the full context of the _hexrun-orchestrator-template.ts file
echo "=== Checking _hexrun-orchestrator-template.ts imports and usage patterns ==="
head -120 src/lib/domains/agentic/templates/_hexrun-orchestrator-template.ts
echo ""
echo "=== Check ItemTypeValue definition and usage context ==="
rg -n "type ItemTypeValue" src/lib/domains/mapping/_objects/map-item.ts -A 5Repository: Diplow/hexframe
Length of output: 4176
🏁 Script executed:
#!/bin/bash
# Check where shouldUseOrchestrator is used
echo "=== Usages of shouldUseOrchestrator function ==="
rg -n "shouldUseOrchestrator" --type=ts -B 2 -A 2
echo ""
echo "=== Check if MapItemType is imported in _hexrun-orchestrator-template.ts ==="
head -15 src/lib/domains/agentic/templates/_hexrun-orchestrator-template.ts | grep -E "import|from"Repository: Diplow/hexframe
Length of output: 2782
Use MapItemType.SYSTEM enum instead of string literal for type safety and consistency.
The string 'system' is factually the correct runtime value of MapItemType.SYSTEM, but using a hardcoded string literal breaks the pattern established throughout the codebase. Other agentic template files (e.g., _prompt-builder.ts, _internals/section-builders.ts) use the MapItemType.SYSTEM enum for comparisons. Replace itemType === 'system' with itemType === MapItemType.SYSTEM and add the required import from ~/lib/domains/mapping/_objects/map-item to maintain consistency, type safety, and catch potential enum changes at compile time.
🤖 Prompt for AI Agents
In @src/lib/domains/agentic/templates/_hexrun-orchestrator-template.ts around
lines 109 - 110, Replace the string literal comparison in shouldUseOrchestrator
with the MapItemType enum: change the check from itemType === 'system' to
itemType === MapItemType.SYSTEM, and add an import for MapItemType from
~/lib/domains/mapping/_objects/map-item so the function uses the enum for type
safety and consistency with other templates.
| export const itemTypeSchema = z.string().min(1).refine( | ||
| (val) => val.toLowerCase() !== "user", | ||
| { message: "The 'user' item type is reserved for system-created root tiles" } | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Add string length limits and stricter validation for custom item types.
The current validation allows any non-empty string except "user", which has several risks:
- No maximum length: Very long strings could cause storage or display issues
- No whitespace handling: Strings like
" "or" organizational "would pass validation - No character restrictions: Special characters, line breaks, or control characters could cause issues
🔎 Proposed validation improvements
-export const itemTypeSchema = z.string().min(1).refine(
- (val) => val.toLowerCase() !== "user",
- { message: "The 'user' item type is reserved for system-created root tiles" }
-);
+export const itemTypeSchema = z
+ .string()
+ .min(1)
+ .max(50)
+ .trim()
+ .regex(/^[a-z0-9_-]+$/i, {
+ message: "Item type must contain only letters, numbers, hyphens, and underscores"
+ })
+ .refine(
+ (val) => val.toLowerCase() !== "user",
+ { message: "The 'user' item type is reserved for system-created root tiles" }
+ );This ensures:
- Maximum 50 characters (reasonable for type names)
- Whitespace is trimmed before validation
- Only alphanumeric, hyphen, and underscore characters allowed
- Case-insensitive "user" check remains
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const itemTypeSchema = z.string().min(1).refine( | |
| (val) => val.toLowerCase() !== "user", | |
| { message: "The 'user' item type is reserved for system-created root tiles" } | |
| ); | |
| export const itemTypeSchema = z | |
| .string() | |
| .min(1) | |
| .max(50) | |
| .trim() | |
| .regex(/^[a-z0-9_-]+$/i, { | |
| message: "Item type must contain only letters, numbers, hyphens, and underscores" | |
| }) | |
| .refine( | |
| (val) => val.toLowerCase() !== "user", | |
| { message: "The 'user' item type is reserved for system-created root tiles" } | |
| ); |
🤖 Prompt for AI Agents
In @src/server/api/routers/map/map-schemas.ts around lines 57 - 60,
itemTypeSchema currently only bans "user" and allows any non-empty string;
update itemTypeSchema to trim input, enforce a max length of 50, reject strings
that become empty after trimming, restrict characters to only alphanumeric,
hyphen and underscore (e.g. /^[A-Za-z0-9_-]+$/), and keep the case-insensitive
"user" prohibition; implement this by replacing the existing
z.string().min(1).refine(...) chain with a pipeline that trims then checks
length <= 50, validates the regex, and refines against val.toLowerCase() ===
"user" (or equivalent) so all checks run in order.
Implement two-phase template rendering with child path syntax:
- child[-3].title, child[-2,-1].content for direct field access
- child[-6..-1], child[1..6] for range iteration
- {{@RenderChildren range=[-6..-1] fallback='generic'}} for pool dispatch
New modules:
- _pre-processor/_path-parser.ts: Parse child path expressions
- _pool/: Template pool types and builder for itemType matching
- _compiler/: Two-phase compile (structural template) + render (Mustache)
Key features:
- Templates match tiles by templateName (title) to itemType
- Recursive depth control via nested template pools
- Variable prefixing: {{title}} → {{child[-3].title}}
- Inspectable compiled templates showing exact prompt structure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI Agents
In @src/lib/domains/agentic/templates/_internals/types.ts:
- Around line 7-15: The PromptDataTile interface duplicates TileData; replace
the duplicate by importing/re-exporting or aliasing TileData: remove
PromptDataTile from src/lib/domains/agentic/templates/_internals/types.ts and
update any usages to reference TileData (from
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts), or alternatively
export TileData from the pre-processor module and import it here so both modules
share the single TileData definition; ensure imports/exports and all references
to PromptDataTile are updated to the TileData symbol to keep types consistent.
🧹 Nitpick comments (5)
src/lib/domains/agentic/templates/_compiler/_variable-prefixer.ts (1)
16-16: Consider enforcing brace symmetry in the regex pattern.The current pattern
/(\{\{\{?)(\w+)(\}\}\}?)/gdoesn't enforce that opening and closing braces match (both 2 or both 3). For example,{{{title}}would be matched even though it's malformed Mustache syntax.Consider using separate patterns or add validation to ensure brace counts match:
🔎 Suggested improvement
-const MUSTACHE_VAR_PATTERN = /(\{\{\{?)(\w+)(\}\}\}?)/g +// Match either {{var}} or {{{var}}} but not mixed +const MUSTACHE_VAR_PATTERN = /(\{\{\{)(\w+)(\}\}\})|(\{\{)(\w+)(\}\})/gOr add validation in the replace callback to verify symmetry.
src/lib/domains/agentic/templates/_pre-processor/_path-parser.ts (1)
119-127: Consider simplifying the direction validation.The null check followed by non-null assertion works but could be cleaner using a type guard pattern.
🔎 Suggested simplification
- // Validate all directions parsed successfully - if (directions.some(d => d === null)) { - return null - } - - // Validate directions are in valid range (-6 to 6, excluding 0) - if (directions.some(d => d === 0 || d! < -6 || d! > 6)) { - return null - } + // Validate all directions parsed successfully and are in valid range + const isValidDirection = (d: number | null): d is number => + d !== null && d !== 0 && d >= -6 && d <= 6 + + if (!directions.every(isValidDirection)) { + return null + }src/lib/domains/agentic/templates/_pool/builder.ts (1)
101-125: Consider extracting duplicate normalization logic.The title normalization pattern
.toLowerCase().replace(/\s+/g, '-')is duplicated on lines 111 and 121.🔎 Extract helper function
+const normalizeTemplateName = (name: string): string => + name.toLowerCase().replace(/\s+/g, '-') + function _getTemplateName(tile: TileData): string | undefined { // Check for templateName in extended tile data const extendedTile = tile as TileData & { templateName?: string } if (extendedTile.templateName) { return extendedTile.templateName } // For template tiles, use title as templateName // (templates have itemType="template" but title indicates what they match) if (tile.itemType === 'template' && tile.title) { - return tile.title.toLowerCase().replace(/\s+/g, '-') + return normalizeTemplateName(tile.title) } // For non-template tiles, use itemType if (tile.itemType && tile.itemType !== 'template') { return String(tile.itemType) } // Last resort: derive from title if (tile.title) { - return tile.title.toLowerCase().replace(/\s+/g, '-') + return normalizeTemplateName(tile.title) } return undefined }src/lib/domains/agentic/templates/_pre-processor/index.ts (1)
138-161: Consider keeping switch in sync with registry.The
_validateTemplateExistsfunction checks iftemplateNameexists inregistry, but_invokeTemplatehas a hardcoded switch. If new templates are added to the registry, the switch must also be updated.This could be simplified by making registry functions have a uniform signature, allowing direct invocation without a switch. Alternatively, add a comment noting the coupling.
🔎 Minimal fix: Add comment noting the coupling
function _invokeTemplate( templateName: string, resolvedParams: ResolvedParams, context: TemplateContext, registry: TemplateRegistry, originalParams: ParsedParams ): string { + // NOTE: This switch must stay in sync with TemplateRegistry keys. + // Adding a new template to the registry requires adding a case here. switch (templateName) {src/lib/domains/agentic/templates/_compiler/index.ts (1)
63-77: Consider making maxDepth configurable.The
maxDepthis hardcoded to 10. Consider adding an optional parameter to allow callers to customize the recursion limit based on their specific needs.🔎 Proposed enhancement to make maxDepth configurable
export function compileTemplate( template: string, taskTile: TileData, - templatePool: TemplatePool + templatePool: TemplatePool, + maxDepth: number = 10 ): string { const context: CompileContext = { currentPath: [], taskTile, templatePool, - maxDepth: 10, + maxDepth, currentDepth: 0 } return _compileWithContext(template, context) }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
src/lib/domains/agentic/templates/__tests__/compiler.test.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_compiler/_render-children.tssrc/lib/domains/agentic/templates/_compiler/_two-phase-render.tssrc/lib/domains/agentic/templates/_compiler/_variable-prefixer.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/templates/_pool/builder.tssrc/lib/domains/agentic/templates/_pool/index.tssrc/lib/domains/agentic/templates/_pool/types.tssrc/lib/domains/agentic/templates/_pre-processor/_path-parser.tssrc/lib/domains/agentic/templates/_pre-processor/_resolver.tssrc/lib/domains/agentic/templates/_pre-processor/index.ts
✅ Files skipped from review due to trivial changes (1)
- src/lib/domains/agentic/templates/tests/compiler.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always use absolute imports with
~/prefix instead of relative imports (./or../), as enforced by ESLintno-restricted-importsrule
Files:
src/lib/domains/agentic/templates/_compiler/_variable-prefixer.tssrc/lib/domains/agentic/templates/_compiler/_render-children.tssrc/lib/domains/agentic/templates/_compiler/_two-phase-render.tssrc/lib/domains/agentic/templates/_pool/builder.tssrc/lib/domains/agentic/templates/_pre-processor/_path-parser.tssrc/lib/domains/agentic/templates/_pre-processor/_resolver.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_pool/types.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_pool/index.ts
src/lib/domains/agentic/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
buildPrompt()function fromsrc/lib/domains/agentic/services/prompt-executor.service.tsto deterministically generate XML prompts from tile coordinates
Files:
src/lib/domains/agentic/templates/_compiler/_variable-prefixer.tssrc/lib/domains/agentic/templates/_compiler/_render-children.tssrc/lib/domains/agentic/templates/_compiler/_two-phase-render.tssrc/lib/domains/agentic/templates/_pool/builder.tssrc/lib/domains/agentic/templates/_pre-processor/_path-parser.tssrc/lib/domains/agentic/templates/_pre-processor/_resolver.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_pool/types.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_pool/index.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Every tile has a semantic type (USER, ORGANIZATIONAL, CONTEXT, SYSTEM) that guides agent behavior; treat unclassified legacy tiles with null itemType as undefined
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to src/lib/domains/agentic/**/*.{ts,tsx} : Use `buildPrompt()` function from `src/lib/domains/agentic/services/prompt-executor.service.ts` to deterministically generate XML prompts from tile coordinates
Applied to files:
src/lib/domains/agentic/templates/_compiler/_two-phase-render.tssrc/lib/domains/agentic/templates/_pool/builder.tssrc/lib/domains/agentic/templates/_pre-processor/_resolver.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_internals/types.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_pool/index.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Tiles can have both subtask children (positive directions 1-6) and context children (negative directions -1 to -6) simultaneously; context children are reference materials not preloaded eagerly
Applied to files:
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Every tile has a semantic type (USER, ORGANIZATIONAL, CONTEXT, SYSTEM) that guides agent behavior; treat unclassified legacy tiles with null itemType as undefined
Applied to files:
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts
🧬 Code graph analysis (9)
src/lib/domains/agentic/templates/_compiler/_variable-prefixer.ts (1)
src/lib/domains/agentic/templates/_compiler/index.ts (3)
prefixVariables(14-14)formatPath(14-14)formatPathWithField(14-14)
src/lib/domains/agentic/templates/_compiler/_render-children.ts (7)
src/lib/domains/agentic/templates/_compiler/index.ts (3)
CompileContext(13-13)compileTemplate(63-77)prefixVariables(14-14)src/lib/domains/agentic/templates/_compiler/types.ts (1)
CompileContext(13-24)src/lib/domains/agentic/templates/_pre-processor/index.ts (4)
getChildrenInRange(15-15)TileData(17-17)ChildPath(26-26)resolveChildPath(13-13)src/lib/domains/agentic/templates/_pre-processor/_path-parser.ts (1)
ChildPath(15-18)src/lib/domains/agentic/templates/_pool/builder.ts (2)
findTemplateForItemType(80-89)buildTemplatePool(19-54)src/lib/domains/agentic/templates/_pool/index.ts (2)
findTemplateForItemType(12-12)buildTemplatePool(10-10)src/lib/domains/agentic/templates/_compiler/_variable-prefixer.ts (1)
prefixVariables(41-67)
src/lib/domains/agentic/templates/_pool/builder.ts (2)
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (1)
TileData(12-20)src/lib/domains/agentic/templates/_pool/types.ts (2)
TemplatePool(27-32)PooledTemplate(15-22)
src/lib/domains/agentic/templates/_pre-processor/_path-parser.ts (1)
src/lib/domains/agentic/templates/_pre-processor/index.ts (8)
ChildPath(26-26)ChildRange(26-26)ParsedChildRef(26-26)parseChildRef(20-20)parseChildPath(21-21)parseChildRange(22-22)formatChildPath(23-23)isChildRef(24-24)
src/lib/domains/agentic/templates/_compiler/types.ts (3)
src/lib/domains/agentic/templates/_compiler/index.ts (2)
CompileContext(13-13)CompileResult(13-13)src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (1)
TileData(12-20)src/lib/domains/agentic/templates/_pool/types.ts (1)
TemplatePool(27-32)
src/lib/domains/agentic/templates/_compiler/_data-builder.ts (2)
src/lib/domains/agentic/templates/_internals/types.ts (2)
PromptData(17-43)PromptDataTile(7-15)src/lib/domains/agentic/templates/_pre-processor/index.ts (1)
TileData(17-17)
src/lib/domains/agentic/templates/_pool/types.ts (1)
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (1)
TileData(12-20)
src/lib/domains/agentic/templates/_internals/types.ts (4)
src/lib/domains/agentic/templates/_prompt-builder.ts (2)
PromptDataTile(30-30)PromptData(30-30)src/lib/domains/mapping/_objects/index.ts (1)
ItemTypeValue(9-9)src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/utils/index.ts (1)
ItemTypeValue(32-32)
src/lib/domains/agentic/templates/_compiler/index.ts (4)
src/lib/domains/agentic/templates/_pre-processor/index.ts (1)
TileData(17-17)src/lib/domains/agentic/templates/_pool/index.ts (1)
TemplatePool(7-7)src/lib/domains/agentic/templates/_compiler/types.ts (1)
CompileContext(13-24)src/lib/domains/agentic/templates/_compiler/_render-children.ts (1)
expandRenderChildren(26-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build
- GitHub Check: Tests - Phase 1 (Main Suite)
- GitHub Check: ESLint
- GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (29)
src/lib/domains/agentic/templates/_compiler/types.ts (1)
1-34: LGTM!The type definitions are clean, well-documented, and use absolute imports correctly. The interfaces appropriately capture the context and results for the two-phase template compilation pipeline.
src/lib/domains/agentic/templates/_compiler/_variable-prefixer.ts (2)
41-67: LGTM!The prefixing logic correctly:
- Skips empty path prefixes (early return)
- Preserves reserved variables unchanged
- Only prefixes tile-specific fields (title, content, preview, coords, itemType)
- Maintains Mustache syntax with correct brace handling
75-88: LGTM!The path formatting helpers are clean and straightforward. They produce consistent child reference syntax for the template system.
src/lib/domains/agentic/templates/_internals/types.ts (1)
17-43: LGTM!The
PromptDatainterface is well-structured with clear documentation. The requireditemTypefield and optional USER-specific fields (discussion,userMessage) appropriately reflect the prompt building requirements.src/lib/domains/agentic/templates/_pool/types.ts (1)
1-32: LGTM!The template pool types are clean and well-documented:
PooledTemplateappropriately captures template metadata and nested childrenTemplatePoolprovides a clear Map-based structure with optional fallback- Uses absolute imports correctly
src/lib/domains/agentic/templates/_pool/index.ts (1)
1-13: LGTM!The barrel file cleanly consolidates the template pool public API:
- Separates type exports from function exports
- Uses absolute imports correctly
- Provides a single entry point for consumers
src/lib/domains/agentic/templates/_pre-processor/_path-parser.ts (2)
1-27: LGTM! Well-structured path parser module.Clean separation of types and well-documented patterns. The union type
ParsedChildRefprovides good type discrimination for downstream consumers.
140-158: LGTM! Range parsing is correct.The validation logic correctly handles:
- NaN checks for parsed integers
- Exclusion of 0 from valid directions
- Boundary validation (-6 to 6)
src/lib/domains/agentic/templates/_compiler/_render-children.ts (3)
8-13: LGTM! Imports follow coding guidelines.All imports correctly use absolute paths with the
~/libprefix as required by the ESLint configuration.
26-55: LGTM! Clean implementation of child expansion.The function correctly:
- Limits recursion depth with a descriptive HTML comment marker
- Handles missing tiles gracefully
- Delegates child rendering with proper context propagation
74-114: LGTM! Child rendering logic is well-structured.The implementation correctly:
- Falls back gracefully when no template matches
- Builds a fresh template pool for nested children
- Increments recursion depth to prevent infinite loops
- Prefixes variables after compilation to maintain correct scoping
One observation: the
fallbackNameparameter is used to build the next-level pool (line 98), ensuring consistent fallback behavior through nested renders.src/lib/domains/agentic/templates/_pool/builder.ts (2)
7-8: LGTM! Imports follow coding guidelines.Correctly uses absolute imports with
~/libprefix.
19-54: LGTM! Pool building logic is correct.The function properly:
- Skips non-template children (those without content)
- Derives template names using the helper function
- Tracks the fallback template when matched
src/lib/domains/agentic/templates/_pre-processor/index.ts (2)
10-27: LGTM! Clean barrel exports.The module correctly re-exports path-parser and resolver utilities, making them available from a single entry point for consumers.
34-44: LGTM! Well-designed error class.The
TemplateErrorincludes all necessary context for debugging: template name, original parameters, and error chaining viacause.src/lib/domains/agentic/templates/_compiler/_data-builder.ts (4)
8-9: LGTM! Imports follow coding guidelines.Correctly uses absolute imports with
~/libprefix.
17-30: LGTM! Well-typed Mustache data interface.The interface correctly models the flat data structure with:
- Typed
taskfield- Index signature for dynamic child path keys
96-110: LGTM! Robust direction extraction.The function correctly handles edge cases:
- Missing path part (no
:separator)- Empty direction string (returns
undefined)- Non-numeric directions (returns
undefinedviaisNaNcheck)
141-169: LGTM! Clean recursive flattening.The implementation correctly:
- Skips children without direction values
- Builds path keys with proper comma-separated format
- Handles missing optional fields with sensible defaults
- Recursively processes all nested children
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (4)
7-8: LGTM! Imports follow coding guidelines.Correctly uses absolute imports with
~/libprefix for both mapping types and path parser.
12-20: LGTM! TileData interface updated correctly.The changes align with the Templates as Tiles system:
itemTypenow usesItemTypeValuefor string-based custom typesdirectionfield enables path-based child navigationBased on learnings, tiles can have both subtask children (positive directions 1-6) and context children (negative directions -1 to -6).
107-124: LGTM! Path resolution is correctly implemented.The function properly handles:
- Multi-level nested paths via direction iteration
- Missing children at any level (returns
undefined)- Direction-based child lookup
154-169: No action needed. The function's behavior is correct and tested.The test at line 131 of
compiler.test.tsexplicitly validates thatgetChildrenInRange(mockTile, -6, -1)returns children sorted in ascending order by direction ([-3, -2]), confirming that always sorting ascending regardless of parameter order is the intended design. The function documentation also states it returns results "sorted by direction."src/lib/domains/agentic/templates/_compiler/_two-phase-render.ts (3)
8-12: LGTM! Imports are correct.All imports use absolute paths with
~/libprefix. Mustache is an external library import which is appropriate.
42-72: LGTM! Clean two-phase render implementation.The function clearly separates:
- Phase 1: Template compilation with pool-based template resolution
- Phase 2: Mustache rendering with flattened tile data
Minor note: The
gflag on line 65 (/\n\n$/g) is unnecessary since$matches end-of-string only once, but it doesn't affect correctness.
82-99: LGTM! Utility functions are straightforward.
compileOnlyprovides a clean inspection API for debugging.
usesPoolBasedRenderingis a simple heuristic - note it may match the tag in comments/strings, but this is acceptable for its intended use case.src/lib/domains/agentic/templates/_compiler/index.ts (3)
8-11: LGTM: Imports follow absolute path guidelines.All imports correctly use the
~/prefix as required by the coding guidelines.
13-28: LGTM: Well-structured public API facade.The re-exports provide a clean public interface for the compiler module, exposing types and utilities while keeping internal helpers private.
98-172: LGTM: Sound implementation with good defensive practices.The implementation correctly:
- Processes tags in reverse order to preserve string positions
- Validates range parameters before proceeding
- Uses optional chaining for optional fallback parameter
- Resets regex
lastIndexto prevent state issuesThe non-null assertions (lines 109, 134, 143-144) are safe due to preceding validation checks.
| export interface PromptDataTile { | ||
| title: string | ||
| content?: string | ||
| preview?: string | ||
| coords: string | ||
| itemType?: ItemTypeValue | ||
| direction?: number // -6 to 6, for child lookup by direction | ||
| children?: PromptDataTile[] | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Consider unifying PromptDataTile with TileData.
The PromptDataTile interface has identical fields to TileData from src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (lines 11-19):
- Both have: title, content?, preview?, coords, itemType?, direction?, children?
Having two identical type definitions creates maintenance overhead. Consider either:
- Re-exporting
TileDatafrom the pre-processor module, or - Using
TileDatadirectly and removingPromptDataTile
This would ensure type consistency across the template system.
🤖 Prompt for AI Agents
In @src/lib/domains/agentic/templates/_internals/types.ts around lines 7 - 15,
The PromptDataTile interface duplicates TileData; replace the duplicate by
importing/re-exporting or aliasing TileData: remove PromptDataTile from
src/lib/domains/agentic/templates/_internals/types.ts and update any usages to
reference TileData (from
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts), or alternatively
export TileData from the pre-processor module and import it here so both modules
share the single TileData definition; ensure imports/exports and all references
to PromptDataTile are updated to the TileData symbol to keep types consistent.
…te context
- Add {{@RenderAncestors fallback='...'}} tag for ancestor pool dispatch
- Allow direction 0 in range for hexplan tiles (range=[0..0])
- Add template context support via template[-1], template[-2], etc.
- Add ancestor path syntax: ancestor[-1] (parent), ancestor[-2] (grandparent)
- Update System Task Template to use:
- {{{template[-1].content}}} for hexrun-intro
- {{@RenderAncestors fallback='ancestor'}} for ancestors
- {{@RenderChildren range=[0..0] fallback='hexplan'}} for hexplan
- Add sub-templates: system (subtasks), ancestor, hexplan
- Add hexrun-intro as composed child of template tile
- Add plan for User Interlocutor Template migration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/lib/domains/agentic/templates/_prompt-builder.ts (1)
42-71: Type narrowing issue: exhaustive check may not work correctly.After the
!isBuiltInItemType(itemType)check on line 49, TypeScript doesn't narrowitemTypetoMapItemTypefor the switch statement. The type remainsItemTypeValue(which isMapItemType | string), making the exhaustive check on line 67 ineffective.♻️ Proposed fix: Add type assertion after the type guard
function _getTemplateByItemType(itemType: ItemTypeValue | null | undefined): string { if (itemType === null || itemType === undefined) { throw new Error(TEMPLATE_LOOKUP_ERRORS.noItemType) } // Handle custom (non-built-in) item types by using SYSTEM template // Custom types like "template" are treated as executable tiles if (!isBuiltInItemType(itemType)) { return SYSTEM_TEMPLATE } + + // TypeScript now knows itemType is MapItemType + const builtInType: MapItemType = itemType - switch (itemType) { + switch (builtInType) { case MapItemType.SYSTEM: return SYSTEM_TEMPLATE case MapItemType.USER: return USER_TEMPLATE case MapItemType.ORGANIZATIONAL: throw new Error(TEMPLATE_LOOKUP_ERRORS.organizationalNotSupported) case MapItemType.CONTEXT: throw new Error(TEMPLATE_LOOKUP_ERRORS.contextNotImplemented) default: { - const exhaustiveCheck: never = itemType + const exhaustiveCheck: never = builtInType throw new Error(`Unknown itemType: ${String(exhaustiveCheck)}`) } } }
🤖 Fix all issues with AI agents
In @src/lib/domains/agentic/templates/_compiler/_render-ancestors.ts:
- Around line 74-79: The ancestor compilation ignores recursion depth: when
compiling ancestor templates via compileTemplate and then calling
prefixAncestorVariables, increment and check the depth in the context (use
context.currentDepth and context.maxDepth) to prevent infinite recursion from
nested {{@RenderAncestors}}; e.g., create a shallow copy of context with
currentDepth: (context.currentDepth || 0) + 1, validate that newCurrentDepth <
context.maxDepth (or throw/skip compilation) before calling
compileTemplate(template.content, newContext), then pass the compiled result to
prefixAncestorVariables so the depth safeguard mirrors the RenderChildren
behavior.
In @src/lib/domains/agentic/templates/_compiler/_render-children.ts:
- Around line 80-87: The initial template lookup in _render-children.ts
currently calls findTemplateForItemType(context.templatePool, itemTypeStr) but
ignores fallbackName; update the code so the template is resolved with fallback
handling just like in _render-ancestors.ts — e.g., set template =
findTemplateForItemType(context.templatePool, itemTypeStr) ?? (fallbackName ?
findTemplateForItemType(context.templatePool, fallbackName) : undefined) —
ensuring you reference findTemplateForItemType, itemTypeStr, fallbackName and
context.templatePool when making the change.
🧹 Nitpick comments (9)
docs/plans/user-template-pool-dispatch.md (3)
17-22: Strengthen target state clarity with explicit benefits and constraints.The target state correctly identifies the three key patterns (RenderChildren, template context, direction-0), but it would benefit from brief explanations of why each is important:
- Why RenderChildren over hardcoded variables?
- What does template context enable beyond current approach?
- Why direction-0 for recent-history (parity with hexplan)?
This added clarity will help reviewers and implementers understand the rationale.
25-37: Task table is high-level; consider documenting interdependencies and success criteria.Tasks 1–6 are clearly listed, but the table lacks:
- Interdependencies (e.g., must task 1 complete before 2, or can they run in parallel?)
- Success/acceptance criteria (e.g., "All sub-templates resolve correctly" or "Backward compatibility with existing userIntro calls")
- Estimated effort or priority order
- Reference to corresponding test coverage (the PR mentions 209+ tests; which ones apply here?)
This would help implementers prioritize and verify completion.
68-68: Document the design decision to keepdiscussionanduserMessageas Mustache variables.The note explains that these remain as Mustache variables but not why. Is this because:
- They're session-level data, not persistently stored tiles?
- They require special handling (e.g., escaping, formatting) that template tiles don't support?
- It's a temporary backward-compatibility measure?
Documenting the rationale will help maintainers understand the boundary between tile-based and variable-based content.
src/lib/domains/agentic/templates/_system-template.ts (1)
30-34: Consider clarifying the relationship between these fields and the{{@HexPlan}}pre-processor tag.The implementation follows existing patterns well. However, the documentation could be enhanced to explain:
- How these fields relate to the
{{@HexPlan}}tag expansion at line 63- What makes this HexPlan "simplified" compared to a full HexPlan
- Whether the pre-processor uses
hasHexplanto conditionally expand/remove the tagThis would help future maintainers understand the data flow from these fields → pre-processor → final template output.
📝 Example documentation enhancement
- // Simplified HexPlan (for tile-based templates) + // Simplified HexPlan (for tile-based templates) + // The pre-processor uses these fields to expand the {{@HexPlan}} tag: + // - If hasHexplan is true, hexPlan content is injected at the {{@HexPlan}} location + // - hexplanCoords provides tile coordinates for the hexplan source hasHexplan: boolean hexplanCoords: string hexPlan: stringsrc/lib/domains/agentic/templates/_prompt-builder.ts (1)
192-194: Unnecessary type assertion.The type assertion
(MapItemType.USER as ItemTypeValue)on line 192 is unnecessary.MapItemType.USERis already assignable toItemTypeValuesinceItemTypeValue = MapItemType | string. The comparison should work directly:data.itemType === MapItemType.USER.♻️ Simplify by removing the type assertion
// Prepare template data based on item type - const templateData = data.itemType === (MapItemType.USER as ItemTypeValue) + const templateData = data.itemType === MapItemType.USER ? _prepareUserTemplateData(data) : _prepareSystemTemplateData(data)src/lib/domains/agentic/templates/_pre-processor/index.ts (1)
147-170: Unreachable default case after template validation.The
_validateTemplateExistsfunction already throws for unknown templates at line 97, making thedefaultcase at line 168 unreachable during normal execution. Consider either:
- Removing the default case and relying on TypeScript's exhaustiveness checking (add a type guard)
- Adding a comment explaining this is defensive programming
The current implementation is safe but the default case is dead code in practice.
♻️ Option: Use exhaustive switch with never type
function _invokeTemplate( templateName: string, resolvedParams: ResolvedParams, context: TemplateContext, registry: TemplateRegistry, originalParams: ParsedParams ): string { + // Assert templateName is a valid registry key (already validated by _validateTemplateExists) + const validName = templateName as keyof TemplateRegistry switch (templateName) { case 'GenericTile': return registry.GenericTile(resolvedParams.item, resolvedParams.fields, resolvedParams.wrapper) case 'Folder': return registry.Folder(resolvedParams.item, resolvedParams.fields, resolvedParams.depth) case 'TileOrFolder': return registry.TileOrFolder(resolvedParams.item, resolvedParams.fields, resolvedParams.wrapper, resolvedParams.depth) case 'HexPlan': return registry.HexPlan(context.hexplanCoords, context.hexPlan, context.hexplanStatus, { mcpServerName: context.mcpServerName, isParentTile: context.isParentTile, taskCoords: context.task.coords, }) default: - throw new TemplateError(`No handler for template "${templateName}"`, templateName, originalParams) + // This should be unreachable after _validateTemplateExists + const _exhaustiveCheck: never = validName + throw new TemplateError(`No handler for template "${templateName}"`, templateName, originalParams) } }src/lib/domains/agentic/templates/_compiler/_render-children.ts (1)
89-93: Silently skipping children without direction may hide data issues.Children without a
directionfield are silently skipped with an empty string. While this prevents errors, it could mask issues where children are missing required direction data. Consider logging a warning or adding observability for debugging template rendering issues.src/lib/domains/agentic/templates/_compiler/index.ts (1)
157-164: Silent skip for malformed RenderChildren tags could mask template errors.Tags matching
{{@RenderChildren ...}}but lacking a validrange=[...]parameter are silently skipped viacontinueat line 163. This could hide authoring errors in templates.Consider logging a warning or returning a visible placeholder (like the max-depth comment) to aid debugging.
♻️ Proposed enhancement for debugging
// Parse range parameter const rangeMatch = RANGE_PATTERN.exec(params) if (!rangeMatch) { + // Could add: console.warn(`RenderChildren tag missing valid range parameter: ${match[0]}`) continue }src/lib/domains/agentic/templates/_compiler/_data-builder.ts (1)
17-30: MustacheData type could be more precise.The index signature
[key: string]: {...} | MustacheData['task']means TypeScript allows any string key, which loses type safety. Thetaskproperty has a different shape (noprevieworitemType) than dynamic entries.Consider using a discriminated union or separate properties for better type safety, though this may require refactoring consumers.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
docs/plans/user-template-pool-dispatch.mdsrc/lib/domains/agentic/templates/__tests__/compiler.test.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_compiler/_render-ancestors.tssrc/lib/domains/agentic/templates/_compiler/_render-children.tssrc/lib/domains/agentic/templates/_compiler/_two-phase-render.tssrc/lib/domains/agentic/templates/_compiler/_variable-prefixer.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_pre-processor/_path-parser.tssrc/lib/domains/agentic/templates/_pre-processor/index.tssrc/lib/domains/agentic/templates/_prompt-builder.tssrc/lib/domains/agentic/templates/_system-template.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- src/lib/domains/agentic/templates/tests/compiler.test.ts
- src/lib/domains/agentic/templates/_compiler/_two-phase-render.ts
- src/lib/domains/agentic/templates/_compiler/_variable-prefixer.ts
- src/lib/domains/agentic/templates/_pre-processor/_path-parser.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always use absolute imports with
~/prefix instead of relative imports (./or../), as enforced by ESLintno-restricted-importsrule
Files:
src/lib/domains/agentic/templates/_system-template.tssrc/lib/domains/agentic/templates/_compiler/_render-ancestors.tssrc/lib/domains/agentic/templates/_compiler/_render-children.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_prompt-builder.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_pre-processor/index.ts
src/lib/domains/agentic/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use
buildPrompt()function fromsrc/lib/domains/agentic/services/prompt-executor.service.tsto deterministically generate XML prompts from tile coordinates
Files:
src/lib/domains/agentic/templates/_system-template.tssrc/lib/domains/agentic/templates/_compiler/_render-ancestors.tssrc/lib/domains/agentic/templates/_compiler/_render-children.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_prompt-builder.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_pre-processor/index.ts
🧠 Learnings (2)
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Applies to src/lib/domains/agentic/**/*.{ts,tsx} : Use `buildPrompt()` function from `src/lib/domains/agentic/services/prompt-executor.service.ts` to deterministically generate XML prompts from tile coordinates
Applied to files:
src/lib/domains/agentic/templates/_compiler/_render-ancestors.tssrc/lib/domains/agentic/templates/_compiler/types.tssrc/lib/domains/agentic/templates/_compiler/_data-builder.tssrc/lib/domains/agentic/templates/_prompt-builder.tssrc/lib/domains/agentic/templates/_compiler/index.tssrc/lib/domains/agentic/templates/_pre-processor/index.ts
📚 Learning: 2026-01-02T18:31:50.216Z
Learnt from: CR
Repo: Diplow/hexframe PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-02T18:31:50.216Z
Learning: Parent tiles have subtask children and orchestrate them; their content is context for human reviewers, not agent instructions
Applied to files:
docs/plans/user-template-pool-dispatch.md
🧬 Code graph analysis (7)
src/lib/domains/agentic/templates/_compiler/_render-ancestors.ts (3)
src/lib/domains/agentic/templates/_compiler/index.ts (2)
CompileContext(14-14)compileTemplate(71-87)src/lib/domains/agentic/templates/_compiler/types.ts (1)
CompileContext(13-26)src/lib/domains/agentic/templates/_compiler/_variable-prefixer.ts (1)
prefixAncestorVariables(102-124)
src/lib/domains/agentic/templates/_compiler/_render-children.ts (7)
src/lib/domains/agentic/templates/_compiler/index.ts (3)
CompileContext(14-14)compileTemplate(71-87)prefixVariables(15-15)src/lib/domains/agentic/templates/_compiler/types.ts (1)
CompileContext(13-26)src/lib/domains/agentic/templates/_pre-processor/index.ts (4)
getChildrenInRange(15-15)TileData(17-17)ChildPath(30-30)resolveChildPath(13-13)src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (3)
getChildrenInRange(154-169)TileData(12-20)resolveChildPath(107-124)src/lib/domains/agentic/templates/_pre-processor/_path-parser.ts (1)
ChildPath(17-20)src/lib/domains/agentic/templates/_pool/builder.ts (2)
findTemplateForItemType(80-89)buildTemplatePool(19-54)src/lib/domains/agentic/templates/_compiler/_variable-prefixer.ts (1)
prefixVariables(42-68)
src/lib/domains/agentic/templates/_compiler/types.ts (3)
src/lib/domains/agentic/templates/_compiler/index.ts (2)
CompileContext(14-14)CompileResult(14-14)src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (1)
TileData(12-20)src/lib/domains/agentic/templates/_pool/types.ts (1)
TemplatePool(27-32)
src/lib/domains/agentic/templates/_compiler/_data-builder.ts (5)
src/lib/domains/agentic/templates/_compiler/index.ts (1)
MustacheData(20-20)src/lib/domains/agentic/templates/_prompt-builder.ts (2)
PromptData(30-30)PromptDataTile(30-30)src/lib/domains/agentic/templates/_internals/types.ts (2)
PromptData(17-43)PromptDataTile(7-15)src/lib/domains/agentic/templates/_pre-processor/index.ts (1)
TileData(17-17)src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (1)
TileData(12-20)
src/lib/domains/agentic/templates/_prompt-builder.ts (4)
src/lib/domains/mapping/_objects/map-item.ts (1)
ItemTypeValue(69-69)src/lib/domains/mapping/infrastructure/map-item/item-type-utils.ts (1)
isBuiltInItemType(34-39)src/lib/domains/agentic/templates/_system-template.ts (1)
SYSTEM_TEMPLATE(42-63)src/lib/domains/agentic/templates/_internals/utils.ts (1)
_hasContent(22-24)
src/lib/domains/agentic/templates/_compiler/index.ts (5)
src/lib/domains/agentic/templates/_pre-processor/index.ts (1)
TileData(17-17)src/lib/domains/agentic/templates/_pool/types.ts (1)
TemplatePool(27-32)src/lib/domains/agentic/templates/_compiler/types.ts (1)
CompileContext(13-26)src/lib/domains/agentic/templates/_compiler/_render-children.ts (1)
expandRenderChildren(26-55)src/lib/domains/agentic/templates/_compiler/_render-ancestors.ts (1)
expandRenderAncestors(28-51)
src/lib/domains/agentic/templates/_pre-processor/index.ts (3)
src/lib/domains/agentic/templates/_pre-processor/_resolver.ts (2)
resolveItemReference(60-98)TemplateContext(22-43)src/lib/domains/agentic/templates/_pre-processor/_parser.ts (1)
ParsedParams(16-22)src/lib/domains/agentic/templates/_templates/index.ts (1)
TemplateRegistry(15-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Tests - Phase 2 (React Components)
- GitHub Check: Build
- GitHub Check: Tests - Phase 1 (Main Suite)
🔇 Additional comments (18)
docs/plans/user-template-pool-dispatch.md (4)
1-4: Clear goal statement, but verify coordinate references with PR implementation.The goal is well-articulated: mirror the System Task Template's pool-based dispatch pattern. However, ensure the coordinate references (User Template at
D1i4gEqbi01JWS2F6I7GUN8ekRiU2mjK,0:1,2,2and System Task Template at[1,2,1]) match the actual tile coordinates in the implementation. The notation mixing absolute (D1i...) and shorthand relative ([1,2,1]) could be confusing if not clarified.
40-68: Confirm range syntax and fallback semantics match implementation.The template content uses RenderChildren with ranges (
[-6..-1],[1..6],[0..0]) and fallbacks ('generic','section','recent-history'). Verify:
- Are range bounds inclusive on both ends? (e.g., does
[-6..-1]include both -6 and -1?)- How are fallback names resolved? (e.g., is
'generic'a tile coordinate or a template name?)- What happens if a child at a position does not exist—does fallback apply per-child, or only if the entire range is empty?
These details are critical for correct rendering behavior.
71-105: Verify sub-template coordinate notation and preview field availability.Sub-templates are defined at relative coordinates
[1]–[5]. Confirm:
- Are these coordinates relative to the parent User Interlocutor Template, or absolute within the tile map?
- In the section sub-template (line 93–95),
previewis referenced as a field. Is this:
- A built-in field on structural children?
- A custom field that must be set during seed/migration?
- Populated dynamically by the template resolver?
- Consistency check: the generic sub-template (lines 86–89) uses
titleandcoords, while others addprevieworcontent. Are these fields always available, or template-specific?This will prevent mismatches between template content and available data.
108-122: Clarify how user-intro composed child (-1) content is persisted and updated.The user-intro content is provided as a reference. Confirm:
- Is this content seeded into tile storage during migration (as a composed child -1)?
- How are future updates to the user-intro text handled? (Semver bump, migration, hardcoded in compiler?)
- Is this specific text meant to be customized per user, workspace, or always identical?
This affects how the document should guide the implementation team.
src/lib/domains/agentic/templates/_prompt-builder.ts (2)
29-31: LGTM: Backward compatibility maintained.The re-export pattern for
PromptDataTileandPromptDataensures backward compatibility while centralizing type definitions in_internals/types. This is good practice when refactoring internal implementations.
47-51: LGTM: Custom item types handled correctly.Non-built-in item types (like
"template") correctly fall back toSYSTEM_TEMPLATE, enabling custom item types to be executable tiles.src/lib/domains/agentic/templates/_pre-processor/index.ts (2)
7-35: LGTM!The expanded exports surface is well-organized, grouping resolver utilities, path-parser functions, and their corresponding types. The use of absolute imports with
~/prefix follows the coding guidelines.
91-113: LGTM - Clean refactoring with improved error handling.The refactored
_expandTagfunction properly delegates to helper functions and wraps non-TemplateErrorexceptions intoTemplateErrorwith cause preservation.src/lib/domains/agentic/templates/_compiler/types.ts (1)
1-36: LGTM!Well-structured type definitions for the two-phase template compilation. The JSDoc comments clearly document each field's purpose, and the types properly reference
TileDataandTemplatePoolfrom their respective modules.src/lib/domains/agentic/templates/_compiler/_render-ancestors.ts (1)
28-51: LGTM - Correct ancestor indexing implementation.The negative index calculation correctly maps array position to ancestor distance:
arrayIndex 0(root) withancestors.length=3→-3arrayIndex 2(parent) withancestors.length=3→-1This aligns with the documented indexing convention.
src/lib/domains/agentic/templates/_compiler/_render-children.ts (1)
26-55: LGTM - Solid recursion protection and child rendering logic.The depth check at lines 33-35 prevents runaway recursion, and the early returns for missing tiles/children are appropriate.
src/lib/domains/agentic/templates/_compiler/index.ts (3)
14-29: LGTM - Clean re-export surface.The index properly consolidates and re-exports types, utilities, and functions from internal modules, providing a cohesive public API for the compiler.
71-87: LGTM - Well-designed public entry point.The
compileTemplatefunction provides a clean API with sensible defaults (empty ancestors, maxDepth of 10), and properly initializes theCompileContext.
108-133: LGTM - Correct reverse-order tag expansion.The implementation correctly:
- Finds all tags of both types
- Sorts by position descending to preserve string indices during replacement
- Expands each tag and splices into the result
This is a robust approach for in-place template expansion.
src/lib/domains/agentic/templates/_compiler/_data-builder.ts (4)
40-62: LGTM - Clean PromptData to TileData conversion.The function properly merges composed and structural children into a unified
TileDatastructure, delegating direction inference to the helper.
113-127: Edge case: Empty path part returns undefined correctly.The function handles edge cases well:
- Missing colon → returns
undefined- Empty directions →
isNaNcheck catches it- Single direction → works correctly
One minor note: if
pathPartis an empty string after the colon (e.g.,"userId,groupId:"), the split produces[""]andparseInt("", 10)returnsNaN, which is correctly handled.
158-187: LGTM - Correct recursive flattening with path tracking.The recursive flattening properly:
- Builds composite path keys like
child[-3,-1]- Skips children without direction (consistent with rendering logic)
- Recursively processes nested children
193-214: LGTM - Correct ancestor negative indexing.The ancestor flattening correctly mirrors the indexing convention used in
_render-ancestors.ts, ensuring consistency between template compilation and data building.
|
|
||
| // Compile the sub-template (no recursion for ancestors - they're flat) | ||
| const compiledSubTemplate = compileTemplate(template.content, context) | ||
|
|
||
| // Prefix variables with the ancestor index | ||
| return prefixAncestorVariables(compiledSubTemplate, negativeIndex) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file in question to understand the compilation logic
cat -n src/lib/domains/agentic/templates/_compiler/_render-ancestors.ts | head -100Repository: Diplow/hexframe
Length of output: 3655
🏁 Script executed:
# Search for RenderAncestors usage across the codebase
rg -n "RenderAncestors" --type ts --type tsx -g '!*.test.ts' -A 3 -B 3 | head -80Repository: Diplow/hexframe
Length of output: 86
🏁 Script executed:
# Look for the compileTemplate function to understand context handling
rg -n "compileTemplate|buildPrompt" --type ts --type tsx -g '!*.test.ts' | head -40Repository: Diplow/hexframe
Length of output: 86
🏁 Script executed:
# Check if there are any depth limits or safeguards in the compilation process
rg -n "depth|recursion|loop|infinite" --type ts --type tsx src/lib/domains/agentic/templates/ -g '!*.test.ts' | head -30Repository: Diplow/hexframe
Length of output: 86
🏁 Script executed:
# Find the compileTemplate function definition
fd -n "_compiler" --type f src/lib/domains/agentic/templates/ | head -20Repository: Diplow/hexframe
Length of output: 289
🏁 Script executed:
# Look for the main compiler file
ls -la src/lib/domains/agentic/templates/_compiler/Repository: Diplow/hexframe
Length of output: 632
🏁 Script executed:
# Search for how context is used in compilation
rg -n "context\.ancestors" src/lib/domains/agentic/templates/ --type ts | head -20Repository: Diplow/hexframe
Length of output: 160
🏁 Script executed:
# Search for RenderAncestors tag parsing/handling
rg -n "RenderAncestors|@RenderAncestors" src/lib/domains/agentic/templates/ --type ts | head -40Repository: Diplow/hexframe
Length of output: 2214
🏁 Script executed:
# Read the main compiler file to understand full compilation flow
cat -n src/lib/domains/agentic/templates/_compiler/index.tsRepository: Diplow/hexframe
Length of output: 8333
🏁 Script executed:
# Look at the two-phase render file for context
cat -n src/lib/domains/agentic/templates/_compiler/_two-phase-render.tsRepository: Diplow/hexframe
Length of output: 4391
🏁 Script executed:
# Check the types to understand CompileContext
cat -n src/lib/domains/agentic/templates/_compiler/types.tsRepository: Diplow/hexframe
Length of output: 1315
🏁 Script executed:
# Check if currentDepth is being checked/incremented anywhere in the compilation
rg -n "currentDepth|maxDepth" src/lib/domains/agentic/templates/_compiler/ --type tsRepository: Diplow/hexframe
Length of output: 690
🏁 Script executed:
# Look at _render-children.ts to see how it handles recursion
cat -n src/lib/domains/agentic/templates/_compiler/_render-children.tsRepository: Diplow/hexframe
Length of output: 4711
🏁 Script executed:
# Search for any depth checking or safeguards
rg -n "depth|maxDepth|currentDepth" src/lib/domains/agentic/templates/ --type tsRepository: Diplow/hexframe
Length of output: 2873
🏁 Script executed:
# Check if expandRenderAncestors increments currentDepth
cat -n src/lib/domains/agentic/templates/_compiler/_render-ancestors.ts | grep -A 30 "function _renderAncestor"Repository: Diplow/hexframe
Length of output: 1079
🏁 Script executed:
# Search for any tests that verify RenderAncestors behavior
fd -n "test" src/lib/domains/agentic/templates/ --type f | head -10Repository: Diplow/hexframe
Length of output: 289
🏁 Script executed:
# Look for any tests specifically testing RenderAncestors with nested templates
rg -n "RenderAncestors" src/lib/domains/agentic/templates/__tests__/ --type ts -A 5 -B 5 | head -60Repository: Diplow/hexframe
Length of output: 41
🏁 Script executed:
# Verify the context is truly unchanged - check what compileTemplate expects
cat -n src/lib/domains/agentic/templates/_compiler/index.ts | sed -n '71,87p'Repository: Diplow/hexframe
Length of output: 520
🏁 Script executed:
# Double-check if currentDepth is ever incremented in ancestor rendering
rg -n "currentDepth" src/lib/domains/agentic/templates/_compiler/_render-ancestors.tsRepository: Diplow/hexframe
Length of output: 41
Apply depth-limiting safeguards to RenderAncestors tag compilation to prevent infinite recursion.
The ancestor template is compiled with an unchanged context object and currentDepth is never incremented (unlike RenderChildren at line 107 of _render-children.ts). If an ancestor template contains a nested {{@RenderAncestors}} tag, compilation will recurse infinitely since the depth check at context.currentDepth >= context.maxDepth is never evaluated for ancestors.
The comment "no recursion for ancestors - they're flat" (line 75) states the design intent but isn't enforced by code. Either increment currentDepth when compiling ancestor templates and add a depth check, or validate/restrict ancestor templates from containing RenderAncestors tags.
🤖 Prompt for AI Agents
In @src/lib/domains/agentic/templates/_compiler/_render-ancestors.ts around
lines 74 - 79, The ancestor compilation ignores recursion depth: when compiling
ancestor templates via compileTemplate and then calling prefixAncestorVariables,
increment and check the depth in the context (use context.currentDepth and
context.maxDepth) to prevent infinite recursion from nested
{{@RenderAncestors}}; e.g., create a shallow copy of context with currentDepth:
(context.currentDepth || 0) + 1, validate that newCurrentDepth <
context.maxDepth (or throw/skip compilation) before calling
compileTemplate(template.content, newContext), then pass the compiled result to
prefixAncestorVariables so the depth safeguard mirrors the RenderChildren
behavior.
| // Find matching template in pool | ||
| const itemTypeStr = child.itemType ? String(child.itemType) : undefined | ||
| const template = findTemplateForItemType(context.templatePool, itemTypeStr) | ||
|
|
||
| if (!template) { | ||
| // No template found, skip this child | ||
| return '' | ||
| } |
There was a problem hiding this comment.
The fallbackName parameter is not used for initial template lookup.
The findTemplateForItemType call at line 82 only passes itemTypeStr, but fallbackName is only used when building the next-level pool at line 98. If the intent is to use fallbackName as a fallback when no template matches the child's itemType, this lookup should also consider it.
Compare with _render-ancestors.ts lines 67-68 which explicitly checks the fallback:
const template = findTemplateForItemType(context.templatePool, itemTypeStr)
?? (fallbackName ? findTemplateForItemType(context.templatePool, fallbackName) : undefined)🐛 Proposed fix for consistent fallback handling
// Find matching template in pool
const itemTypeStr = child.itemType ? String(child.itemType) : undefined
- const template = findTemplateForItemType(context.templatePool, itemTypeStr)
+ const template = findTemplateForItemType(context.templatePool, itemTypeStr)
+ ?? (fallbackName ? findTemplateForItemType(context.templatePool, fallbackName) : undefined)
if (!template) {
// No template found, skip this child
return ''
}🤖 Prompt for AI Agents
In @src/lib/domains/agentic/templates/_compiler/_render-children.ts around lines
80 - 87, The initial template lookup in _render-children.ts currently calls
findTemplateForItemType(context.templatePool, itemTypeStr) but ignores
fallbackName; update the code so the template is resolved with fallback handling
just like in _render-ancestors.ts — e.g., set template =
findTemplateForItemType(context.templatePool, itemTypeStr) ?? (fallbackName ?
findTemplateForItemType(context.templatePool, fallbackName) : undefined) —
ensuring you reference findTemplateForItemType, itemTypeStr, fallbackName and
context.templatePool when making the change.
Remove the restriction that prevented user tiles (tiles with empty path) from being compose expanded. This enables users to add composed children to their root tile, which is now relevant for the template system. Changes: - Remove isUserTile check from canShowComposition in MapUI.tsx - Remove isUserTile check from DynamicFrameCore.tsx - Remove isUserTile check from BaseFrame.tsx - Update test to expect user tiles can now show composition 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @src/app/map/Canvas/DynamicFrameCore.tsx:
- Line 532: The DynamicFrameCore currently sets canShowComposition =
isCompositionExpanded && slotScale > 1 without checking for existing composition
children, causing behavior to differ from BaseFrame; update DynamicFrameCore to
compute hasComposition via _hasCompositionChild(center, mapItems) and change
canShowComposition to isCompositionExpanded && hasComposition && slotScale > 1
(and update or remove the "show direction 0 child (or empty)" comment to reflect
the aligned behavior). Ensure you reference the same helper
(_hasCompositionChild) and variables (isCompositionExpanded, slotScale, center,
mapItems) when making the change.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/app/map/Canvas/DynamicFrameCore.tsxsrc/app/map/Canvas/Tile/Base/BaseFrame.tsxsrc/app/map/Canvas/__tests__/TileContextMenu-composition.test.tsxsrc/app/map/_components/MapUI.tsx
💤 Files with no reviewable changes (1)
- src/app/map/_components/MapUI.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Always use absolute imports with
~/prefix instead of relative imports (./or../), as enforced by ESLintno-restricted-importsrule
Files:
src/app/map/Canvas/__tests__/TileContextMenu-composition.test.tsxsrc/app/map/Canvas/DynamicFrameCore.tsxsrc/app/map/Canvas/Tile/Base/BaseFrame.tsx
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use Vitest for tests (not Jest)
Files:
src/app/map/Canvas/__tests__/TileContextMenu-composition.test.tsx
src/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
src/app/**/*.{ts,tsx}: Use Next.js 15 App Router with progressive enhancement, following Static → Progressive → Dynamic component patterns
Use localStorage caching for performance optimization in frontend code
Files:
src/app/map/Canvas/__tests__/TileContextMenu-composition.test.tsxsrc/app/map/Canvas/DynamicFrameCore.tsxsrc/app/map/Canvas/Tile/Base/BaseFrame.tsx
🧬 Code graph analysis (1)
src/app/map/Canvas/Tile/Base/BaseFrame.tsx (1)
src/lib/domains/mapping/services/_item-services/_item-query.service.ts (1)
hasComposition(62-89)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Tests - Phase 1 (Main Suite)
- GitHub Check: Build
- GitHub Check: Tests - Phase 2 (React Components)
- GitHub Check: ESLint
🔇 Additional comments (2)
src/app/map/Canvas/Tile/Base/BaseFrame.tsx (1)
43-43: LGTM! Composition logic correctly enables user tiles.The simplified condition properly removes the user tile restriction as intended by the PR objectives, while maintaining essential checks for expansion state, composition child existence, and sufficient scale.
src/app/map/Canvas/__tests__/TileContextMenu-composition.test.tsx (1)
189-220: LGTM! Test correctly validates user tile composition behavior.The test update properly reflects the new capability for user tiles (empty path) to display composition when
canShowCompositionis true. The test data and expectations are clear and correctly structured.
| const isCompositionExpanded = props.isCompositionExpanded ?? false; | ||
| const isUserTile = item.metadata.coordinates.path.length === 0; | ||
| const canShowComposition = isCompositionExpanded && slotScale > 1 && !isUserTile; | ||
| const canShowComposition = isCompositionExpanded && slotScale > 1; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for hasComposition usage patterns in composition-related code
rg -nP --type=tsx --type=ts -C3 '\bhasComposition\b' -g '!**/node_modules/**' -g '!**/*.test.*'
# Search for canShowComposition patterns to see consistency
rg -nP --type=tsx --type=ts -C2 '\bcanShowComposition\s*=' -g '!**/node_modules/**' -g '!**/*.test.*'Repository: Diplow/hexframe
Length of output: 114
🏁 Script executed:
# First, check if the files exist and get their structure
fd -t f -e tsx -e ts | grep -E '(BaseFrame|DynamicFrameCore)' | head -20
# Get line counts to understand file sizes
wc -l src/app/map/Canvas/BaseFrame.tsx src/app/map/Canvas/DynamicFrameCore.tsx 2>/dev/null || echo "Files not found yet"Repository: Diplow/hexframe
Length of output: 289
🏁 Script executed:
# Let's search more broadly for these files
fd -t f 'BaseFrame\.(tsx|ts)' 2>/dev/null
fd -t f 'DynamicFrameCore\.(tsx|ts)' 2>/dev/nullRepository: Diplow/hexframe
Length of output: 141
🏁 Script executed:
# Read BaseFrame.tsx around line 43 to see the composition logic
sed -n '35,55p' src/app/map/Canvas/Tile/Base/BaseFrame.tsxRepository: Diplow/hexframe
Length of output: 720
🏁 Script executed:
# Read DynamicFrameCore.tsx around line 532 to see the composition logic
sed -n '525,540p' src/app/map/Canvas/DynamicFrameCore.tsxRepository: Diplow/hexframe
Length of output: 755
🏁 Script executed:
# Search for hasComposition in both files
echo "=== hasComposition in BaseFrame.tsx ===" && rg 'hasComposition' src/app/map/Canvas/Tile/Base/BaseFrame.tsx -n -B2 -A2
echo "=== hasComposition in DynamicFrameCore.tsx ===" && rg 'hasComposition' src/app/map/Canvas/DynamicFrameCore.tsx -n -B2 -A2Repository: Diplow/hexframe
Length of output: 646
🏁 Script executed:
# Search for _hasCompositionChild usage in DynamicFrameCore
rg '_hasCompositionChild' src/app/map/Canvas/DynamicFrameCore.tsx -n -B3 -A3Repository: Diplow/hexframe
Length of output: 41
🏁 Script executed:
# Look at more context around line 532 in DynamicFrameCore to understand the composition handling
sed -n '520,560p' src/app/map/Canvas/DynamicFrameCore.tsxRepository: Diplow/hexframe
Length of output: 1536
🏁 Script executed:
# Check if CompositionFrame is imported and how it's used
rg 'CompositionFrame' src/app/map/Canvas/DynamicFrameCore.tsx -n -B2 -A2Repository: Diplow/hexframe
Length of output: 477
🏁 Script executed:
# Search for composition-related logic in DynamicFrameCore
rg 'composition|Composition' src/app/map/Canvas/DynamicFrameCore.tsx -n -i -C2Repository: Diplow/hexframe
Length of output: 3273
Inconsistency confirmed: BaseFrame and DynamicFrameCore handle composition display differently.
BaseFrame.tsx (line 43) checks hasComposition before rendering composition:
const hasComposition = _hasCompositionChild(center, mapItems);
const canShowComposition = isExpanded && hasComposition && scale > 1;
DynamicFrameCore.tsx (line 532) omits this check:
const canShowComposition = isCompositionExpanded && slotScale > 1;
DynamicFrameCore intentionally renders empty composition frames (see comment "show direction 0 child (or empty)" at line 535), allowing users to populate composition slots. This is architecturally different from BaseFrame, which only renders composition when children exist.
Clarify whether this difference is intentional or should be aligned with BaseFrame's validation logic.
🤖 Prompt for AI Agents
In @src/app/map/Canvas/DynamicFrameCore.tsx at line 532, The DynamicFrameCore
currently sets canShowComposition = isCompositionExpanded && slotScale > 1
without checking for existing composition children, causing behavior to differ
from BaseFrame; update DynamicFrameCore to compute hasComposition via
_hasCompositionChild(center, mapItems) and change canShowComposition to
isCompositionExpanded && hasComposition && slotScale > 1 (and update or remove
the "show direction 0 child (or empty)" comment to reflect the aligned
behavior). Ensure you reference the same helper (_hasCompositionChild) and
variables (isCompositionExpanded, slotScale, center, mapItems) when making the
change.
Complete implementation of tile-based template storage with 6 subtasks:
Extend itemType to accept string values
Add templateName column
Implement Template Resolver Service
Migrate Built-in Templates to Tile Storage
Update buildPrompt to Use Tile-Based Templates
Add User Template Allowlist Enforcement
Architecture improvements (Rule-of-6 compliance):
Total: 209+ new tests
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Infrastructure
Documentation
Tests
Behavior
✏️ Tip: You can customize this high-level summary in your review settings.