-
Notifications
You must be signed in to change notification settings - Fork 11
Add PDF support via ct-file-input base component #2151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5 issues found across 5 files
Prompt for AI agents (all 5 issues)
Understand the root cause of the following 5 issues and fix them.
<file name="packages/ui/src/v2/components/ct-file-input/ct-file-input.ts">
<violation number="1" location="packages/ui/src/v2/components/ct-file-input/ct-file-input.ts:235">
maxSizeBytes is exposed and documented but never used, so developers cannot enforce the advertised size warning threshold.</violation>
<violation number="2" location="packages/ui/src/v2/components/ct-file-input/ct-file-input.ts:431">
Max-file guard counts existing files even when multiple uploads are disabled, so single-file inputs with a max cannot replace their selection.</violation>
</file>
<file name="test-file-input.html">
<violation number="1" location="test-file-input.html:87">
The test HTML imports the TypeScript source file directly, so the browser hits a SyntaxError (e.g., at the `type TemplateResult` import) and the components never load. Import the compiled JS bundle instead of the .ts source or serve the page through a build step that transpiles TypeScript.</violation>
</file>
<file name="packages/ui/src/v2/components/ct-image-input/ct-image-input.ts">
<violation number="1" location="packages/ui/src/v2/components/ct-image-input/ct-image-input.ts:194">
The `<input>` change handler now points to a CustomEvent listener that overwrites CTFileInput’s private `_handleFileChange`, so file selections throw (no `detail` on native change events) and no images are ever processed/emitted.</violation>
<violation number="2" location="packages/ui/src/v2/components/ct-image-input/ct-image-input.ts:223">
Listening for `ct-change`/`ct-remove` on the element and re‑emitting the same event name inside the handler causes infinite recursion—the handler catches its own re‑emitted events, so change/remove events can never escape the component.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
| disabled = false; | ||
|
|
||
| @property({ type: Number }) | ||
| maxSizeBytes?: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maxSizeBytes is exposed and documented but never used, so developers cannot enforce the advertised size warning threshold.
Prompt for AI agents
Address the following comment on packages/ui/src/v2/components/ct-file-input/ct-file-input.ts at line 235:
<comment>maxSizeBytes is exposed and documented but never used, so developers cannot enforce the advertised size warning threshold.</comment>
<file context>
@@ -0,0 +1,535 @@
+ disabled = false;
+
+ @property({ type: Number })
+ maxSizeBytes?: number;
+
+ @property({ type: Array })
</file context>
✅ Addressed in b95c1ba
| const currentFiles = this.getFiles(); | ||
|
|
||
| // Check max files limit | ||
| if (this.maxFiles && currentFiles.length + files.length > this.maxFiles) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Max-file guard counts existing files even when multiple uploads are disabled, so single-file inputs with a max cannot replace their selection.
Prompt for AI agents
Address the following comment on packages/ui/src/v2/components/ct-file-input/ct-file-input.ts at line 431:
<comment>Max-file guard counts existing files even when multiple uploads are disabled, so single-file inputs with a max cannot replace their selection.</comment>
<file context>
@@ -0,0 +1,535 @@
+ const currentFiles = this.getFiles();
+
+ // Check max files limit
+ if (this.maxFiles && currentFiles.length + files.length > this.maxFiles) {
+ this.emit("ct-error", {
+ error: new Error("Max files exceeded"),
</file context>
✅ Addressed in b95c1ba
test-file-input.html
Outdated
|
|
||
| <script type="module"> | ||
| // Import components | ||
| import './packages/ui/src/v2/components/ct-file-input/ct-file-input.ts'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test HTML imports the TypeScript source file directly, so the browser hits a SyntaxError (e.g., at the type TemplateResult import) and the components never load. Import the compiled JS bundle instead of the .ts source or serve the page through a build step that transpiles TypeScript.
Prompt for AI agents
Address the following comment on test-file-input.html at line 87:
<comment>The test HTML imports the TypeScript source file directly, so the browser hits a SyntaxError (e.g., at the `type TemplateResult` import) and the components never load. Import the compiled JS bundle instead of the .ts source or serve the page through a build step that transpiles TypeScript.</comment>
<file context>
@@ -0,0 +1,146 @@
+
+ <script type="module">
+ // Import components
+ import './packages/ui/src/v2/components/ct-file-input/ct-file-input.ts';
+ import './packages/ui/src/v2/components/ct-image-input/ct-image-input.ts';
+
</file context>
✅ Addressed in b95c1ba
| this._handleFileChange = (e: CustomEvent) => { | ||
| const detail = e.detail as { files: ImageData[] }; | ||
| // Re-emit with 'images' property for backward compatibility | ||
| this.emit("ct-change", { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Listening for ct-change/ct-remove on the element and re‑emitting the same event name inside the handler causes infinite recursion—the handler catches its own re‑emitted events, so change/remove events can never escape the component.
Prompt for AI agents
Address the following comment on packages/ui/src/v2/components/ct-image-input/ct-image-input.ts at line 223:
<comment>Listening for `ct-change`/`ct-remove` on the element and re‑emitting the same event name inside the handler causes infinite recursion—the handler catches its own re‑emitted events, so change/remove events can never escape the component.</comment>
<file context>
@@ -338,175 +138,109 @@ export class CTImageInput extends BaseElement {
+ this._handleFileChange = (e: CustomEvent) => {
+ const detail = e.detail as { files: ImageData[] };
+ // Re-emit with 'images' property for backward compatibility
+ this.emit("ct-change", {
+ images: detail.files,
+ files: detail.files, // Also include new property
</file context>
✅ Addressed in b95c1ba
| ?multiple="${this.multiple}" | ||
| ?disabled="${this.disabled}" | ||
| capture="${ifDefined(captureAttr)}" | ||
| @change="${this._handleFileChange}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The <input> change handler now points to a CustomEvent listener that overwrites CTFileInput’s private _handleFileChange, so file selections throw (no detail on native change events) and no images are ever processed/emitted.
Prompt for AI agents
Address the following comment on packages/ui/src/v2/components/ct-image-input/ct-image-input.ts at line 194:
<comment>The `<input>` change handler now points to a CustomEvent listener that overwrites CTFileInput’s private `_handleFileChange`, so file selections throw (no `detail` on native change events) and no images are ever processed/emitted.</comment>
<file context>
@@ -338,175 +138,109 @@ export class CTImageInput extends BaseElement {
+ ?multiple="${this.multiple}"
+ ?disabled="${this.disabled}"
+ capture="${ifDefined(captureAttr)}"
+ @change="${this._handleFileChange}"
+ />
+ `;
</file context>
✅ Addressed in b95c1ba
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
11 issues found across 100 files (reviewed changes from recent commits).
Prompt for AI agents (all 11 issues)
Understand the root cause of the following 11 issues and fix them.
<file name="test-shopping-list-mcp.ts">
<violation number="1" location="test-shopping-list-mcp.ts:4">
Avoid hard-coding a machine-specific screenshot directory; use a configurable or relative path so the test can run on any environment.</violation>
<violation number="2" location="test-shopping-list-mcp.ts:158">
Errors are swallowed in the catch block, so the test still exits successfully even when navigation or UI actions fail.</violation>
</file>
<file name="emoji-garden.tsx">
<violation number="1" location="emoji-garden.tsx:142">
Zero-valued coordinates are treated as missing, so the first row/column of the grid can never be planted.</violation>
<violation number="2" location="emoji-garden.tsx:207">
Harvest handler checks the stale stored stage, so plants can never be harvested even after they mature.</violation>
</file>
<file name="simple-garden.tsx">
<violation number="1" location="simple-garden.tsx:153">
The click handler always re-derives a `Cell` and tests it directly, so the add-plant branch is unreachable and no new plants can ever be added.</violation>
</file>
<file name="test-shopping-list.ts">
<violation number="1" location="test-shopping-list.ts:4">
The screenshot directory is hard-coded to a developer-specific absolute path, so the test immediately fails on any other machine when Playwright tries to save a screenshot. Use a repo-relative or configurable directory instead.</violation>
</file>
<file name="east-coast-recipe-enhanced.tsx">
<violation number="1" location="east-coast-recipe-enhanced.tsx:252">
The "Add new page" handler reads `detail.value`, but ct-message-input emits the typed text in `detail.message`, so all new pages are saved as "untitled". Read the `message` property (and update the handler typing) instead.</violation>
<violation number="2" location="east-coast-recipe-enhanced.tsx:272">
Newly added list items never set the required `done` field, so completion state stays undefined and schema validation can fail. Initialize new items with `done: false`.</violation>
</file>
<file name="emoji-garden-simple.tsx">
<violation number="1" location="emoji-garden-simple.tsx:243">
Convert the derived cell to its value before branching; otherwise empty tiles can never plant and watering uses undefined IDs.</violation>
</file>
<file name="emoji-garden-v2.tsx">
<violation number="1" location="emoji-garden-v2.tsx:241">
The click handler treats the derived Cell as if it were the actual Plant, so the planting/watering/harvest logic never executes—`if (!plant)` can never fire and the other branches pass `undefined` ids. Use the actual value (e.g. `const plant = plantAtSpot.get()` or read from `currentPlants.get()`) before branching.</violation>
</file>
<file name="updated-chatbot-check.tsx/simple-chatbot.tsx">
<violation number="1" location="updated-chatbot-check.tsx/simple-chatbot.tsx:118">
The code treats the object returned by `llm()` as a plain string (`String(response.result)`), so it never subscribes to `llmResponse.result` and the assistant message is never shown.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
test-shopping-list-mcp.ts
Outdated
| import { chromium, type Browser, type Page, type BrowserContext } from "npm:playwright@1.40.1"; | ||
|
|
||
| const URL = "http://localhost:8000/alex-112-claude-1/baedreicsz5frwn57hk6t7lnwvgkxbadeqy74ook3x72anz52j44wr5qgcu"; | ||
| const SCREENSHOT_DIR = "/Users/alex/Code/labs/.playwright-mcp"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid hard-coding a machine-specific screenshot directory; use a configurable or relative path so the test can run on any environment.
Prompt for AI agents
Address the following comment on test-shopping-list-mcp.ts at line 4:
<comment>Avoid hard-coding a machine-specific screenshot directory; use a configurable or relative path so the test can run on any environment.</comment>
<file context>
@@ -0,0 +1,175 @@
+import { chromium, type Browser, type Page, type BrowserContext } from "npm:playwright@1.40.1";
+
+const URL = "http://localhost:8000/alex-112-claude-1/baedreicsz5frwn57hk6t7lnwvgkxbadeqy74ook3x72anz52j44wr5qgcu";
+const SCREENSHOT_DIR = "/Users/alex/Code/labs/.playwright-mcp";
+
+async function testShoppingList() {
</file context>
| const SCREENSHOT_DIR = "/Users/alex/Code/labs/.playwright-mcp"; | |
| const SCREENSHOT_DIR = process.env.SCREENSHOT_DIR ?? "./.playwright-mcp"; |
✅ Addressed in 3f75046
emoji-garden.tsx
Outdated
| if (plantIndex === -1) return; | ||
|
|
||
| const plant = plants[plantIndex]; | ||
| if (plant.stage !== PlantStage.PLANT) return; // Can only harvest mature plants |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harvest handler checks the stale stored stage, so plants can never be harvested even after they mature.
Prompt for AI agents
Address the following comment on emoji-garden.tsx at line 207:
<comment>Harvest handler checks the stale stored stage, so plants can never be harvested even after they mature.</comment>
<file context>
@@ -0,0 +1,492 @@
+ if (plantIndex === -1) return;
+
+ const plant = plants[plantIndex];
+ if (plant.stage !== PlantStage.PLANT) return; // Can only harvest mature plants
+
+ plants.splice(plantIndex, 1); // Remove the plant
</file context>
| if (plant.stage !== PlantStage.PLANT) return; // Can only harvest mature plants | |
| if (getPlantStage(plant.plantedAt, plant.wateredAt, Date.now()) !== PlantStage.PLANT) return; // Can only harvest mature plants |
✅ Addressed in 3f75046
emoji-garden.tsx
Outdated
| { detail: { x: number; y: number; player: string } }, | ||
| { plants: Plant[]; selectedPlantType: string; gardenHistory: any[] } | ||
| >(({ detail }, { plants, selectedPlantType, gardenHistory }) => { | ||
| if (!detail?.x || !detail?.y || !detail?.player) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zero-valued coordinates are treated as missing, so the first row/column of the grid can never be planted.
Prompt for AI agents
Address the following comment on emoji-garden.tsx at line 142:
<comment>Zero-valued coordinates are treated as missing, so the first row/column of the grid can never be planted.</comment>
<file context>
@@ -0,0 +1,492 @@
+ { detail: { x: number; y: number; player: string } },
+ { plants: Plant[]; selectedPlantType: string; gardenHistory: any[] }
+>(({ detail }, { plants, selectedPlantType, gardenHistory }) => {
+ if (!detail?.x || !detail?.y || !detail?.player) return;
+
+ // Check if there's already a plant at this position (within 1 grid unit)
</file context>
| if (!detail?.x || !detail?.y || !detail?.player) return; | |
| if (typeof detail?.x !== "number" || typeof detail?.y !== "number" || !detail?.player) return; |
✅ Addressed in 3f75046
simple-garden.tsx
Outdated
| font-size: 1.8rem; | ||
| " | ||
| onClick={() => { | ||
| const plant = derive(plants, (plantsArray) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The click handler always re-derives a Cell and tests it directly, so the add-plant branch is unreachable and no new plants can ever be added.
Prompt for AI agents
Address the following comment on simple-garden.tsx at line 153:
<comment>The click handler always re-derives a `Cell` and tests it directly, so the add-plant branch is unreachable and no new plants can ever be added.</comment>
<file context>
@@ -0,0 +1,182 @@
+ font-size: 1.8rem;
+ "
+ onClick={() => {
+ const plant = derive(plants, (plantsArray) =>
+ plantsArray.find(p => p.x === x && p.y === y)
+ );
</file context>
✅ Addressed in 3f75046
test-shopping-list.ts
Outdated
| import { chromium, type Browser, type Page, type BrowserContext } from "npm:playwright@1.40.1"; | ||
|
|
||
| const URL = "http://localhost:8000/alex-112-claude-1/baedreicsz5frwn57hk6t7lnwvgkxbadeqy74ook3x72anz52j44wr5qgcu"; | ||
| const SCREENSHOT_DIR = "/Users/alex/Code/labs/.playwright-mcp"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The screenshot directory is hard-coded to a developer-specific absolute path, so the test immediately fails on any other machine when Playwright tries to save a screenshot. Use a repo-relative or configurable directory instead.
Prompt for AI agents
Address the following comment on test-shopping-list.ts at line 4:
<comment>The screenshot directory is hard-coded to a developer-specific absolute path, so the test immediately fails on any other machine when Playwright tries to save a screenshot. Use a repo-relative or configurable directory instead.</comment>
<file context>
@@ -0,0 +1,168 @@
+import { chromium, type Browser, type Page, type BrowserContext } from "npm:playwright@1.40.1";
+
+const URL = "http://localhost:8000/alex-112-claude-1/baedreicsz5frwn57hk6t7lnwvgkxbadeqy74ook3x72anz52j44wr5qgcu";
+const SCREENSHOT_DIR = "/Users/alex/Code/labs/.playwright-mcp";
+
+async function testShoppingList() {
</file context>
| const SCREENSHOT_DIR = "/Users/alex/Code/labs/.playwright-mcp"; | |
| const SCREENSHOT_DIR = ".playwright-mcp"; |
✅ Addressed in 3f75046
east-coast-recipe-enhanced.tsx
Outdated
| { list: { title: string; items: any[] } } | ||
| >((event, { list }) => { | ||
| const item = event.detail?.message?.trim(); | ||
| if (item) list.items.push({ title: item }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Newly added list items never set the required done field, so completion state stays undefined and schema validation can fail. Initialize new items with done: false.
Prompt for AI agents
Address the following comment on east-coast-recipe-enhanced.tsx at line 272:
<comment>Newly added list items never set the required `done` field, so completion state stays undefined and schema validation can fail. Initialize new items with `done: false`.</comment>
<file context>
@@ -0,0 +1,501 @@
+ { list: { title: string; items: any[] } }
+>((event, { list }) => {
+ const item = event.detail?.message?.trim();
+ if (item) list.items.push({ title: item });
+});
+
</file context>
| if (item) list.items.push({ title: item }); | |
| if (item) list.items.push({ title: item, done: false }); |
✅ Addressed in 3f75046
emoji-garden-simple.tsx
Outdated
| font-size: 1.5rem; | ||
| " | ||
| onClick={() => { | ||
| const plant = plantAtSpot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Convert the derived cell to its value before branching; otherwise empty tiles can never plant and watering uses undefined IDs.
Prompt for AI agents
Address the following comment on emoji-garden-simple.tsx at line 243:
<comment>Convert the derived cell to its value before branching; otherwise empty tiles can never plant and watering uses undefined IDs.</comment>
<file context>
@@ -0,0 +1,293 @@
+ font-size: 1.5rem;
+ "
+ onClick={() => {
+ const plant = plantAtSpot;
+ if (!plant) {
+ // Plant new seed
</file context>
| const plant = plantAtSpot; | |
| const plant = plantAtSpot.get(); |
✅ Addressed in 3f75046
emoji-garden-v2.tsx
Outdated
| font-size: 1.5rem; | ||
| " | ||
| onClick={() => { | ||
| const plant = derive(currentPlants, (plantsArray) => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The click handler treats the derived Cell as if it were the actual Plant, so the planting/watering/harvest logic never executes—if (!plant) can never fire and the other branches pass undefined ids. Use the actual value (e.g. const plant = plantAtSpot.get() or read from currentPlants.get()) before branching.
Prompt for AI agents
Address the following comment on emoji-garden-v2.tsx at line 241:
<comment>The click handler treats the derived Cell as if it were the actual Plant, so the planting/watering/harvest logic never executes—`if (!plant)` can never fire and the other branches pass `undefined` ids. Use the actual value (e.g. `const plant = plantAtSpot.get()` or read from `currentPlants.get()`) before branching.</comment>
<file context>
@@ -0,0 +1,294 @@
+ font-size: 1.5rem;
+ "
+ onClick={() => {
+ const plant = derive(currentPlants, (plantsArray) =>
+ plantsArray.find(p => Math.floor(p.x) === x && Math.floor(p.y) === y)
+ );
</file context>
✅ Addressed in 3f75046
test-shopping-list-mcp.ts
Outdated
| console.log("Waiting 3 seconds before closing..."); | ||
| await page.waitForTimeout(3000); | ||
|
|
||
| } catch (error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Errors are swallowed in the catch block, so the test still exits successfully even when navigation or UI actions fail.
Prompt for AI agents
Address the following comment on test-shopping-list-mcp.ts at line 158:
<comment>Errors are swallowed in the catch block, so the test still exits successfully even when navigation or UI actions fail.</comment>
<file context>
@@ -0,0 +1,175 @@
+ console.log("Waiting 3 seconds before closing...");
+ await page.waitForTimeout(3000);
+
+ } catch (error) {
+ console.error("\nTEST FAILED:");
+ console.error(error);
</file context>
✅ Addressed in 3f75046
| const lastResponse = derive(aiResponse, (response) => { | ||
| if (!response) return ""; | ||
| if (typeof response === "string") return response; | ||
| if (response.result) return String(response.result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code treats the object returned by llm() as a plain string (String(response.result)), so it never subscribes to llmResponse.result and the assistant message is never shown.
Prompt for AI agents
Address the following comment on updated-chatbot-check.tsx/simple-chatbot.tsx at line 118:
<comment>The code treats the object returned by `llm()` as a plain string (`String(response.result)`), so it never subscribes to `llmResponse.result` and the assistant message is never shown.</comment>
<file context>
@@ -0,0 +1,225 @@
+ const lastResponse = derive(aiResponse, (response) => {
+ if (!response) return "";
+ if (typeof response === "string") return response;
+ if (response.result) return String(response.result);
+ return "";
+ });
</file context>
✅ Addressed in 3f75046
Create generic file upload component that can handle any file type. Features: - Accepts any file type via 'accept' attribute - Smart preview rendering based on MIME type (images vs documents) - Protected methods for subclass extension (processFile, renderPreview, etc.) - No compression logic in base class - Cell-based reactive data binding This component serves as the base for ct-image-input and enables PDF/document upload use cases. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Convert ct-image-input from standalone component to subclass of ct-file-input. Changes: - Extends CTFileInput instead of BaseElement - ImageData now extends FileData - Compression logic moved to override methods - Image dimension extraction in processFile override - Capture attribute added via renderFileInput override - Backward compatibility maintained via property aliases (images/maxImages) - Event details include both 'images' and 'files' properties This reduces code duplication and establishes clear inheritance hierarchy while maintaining full backward compatibility with existing code. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Create HTML test page to verify: - ct-file-input with PDF support - ct-file-input with mixed image/PDF support - ct-image-input backward compatibility (images property) - ct-image-input camera capture support Test file includes event listeners to verify both 'images' and 'files' properties are present for backward compatibility. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
Add TypeScript JSX definitions for ct-file-input: - CTFileInputElement interface - CTFileInputAttributes interface with all properties - IntrinsicElements entry for JSX support This enables type-safe usage of ct-file-input in patterns. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
1. **maxSizeBytes now used in ct-file-input**: Added console.warn when files exceed size threshold 2. **max-file guard fixed**: Only enforces limit in multiple mode, allowing single-file replacement 3. **Removed test-file-input.html**: Browser cannot load TypeScript directly; test-file-upload.tsx pattern serves this purpose 4. **Fixed ct-image-input handler conflict**: Made parent _handleFileChange protected, subclass calls via super 5. **Fixed event recursion**: Override emit() method to add backward-compat properties instead of listening/re-emitting These changes ensure proper file validation, fix single-file selection replacement, and eliminate infinite recursion in event handling. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
- Move maxSizeBytes check to after compression so it validates the final file size - Fix max file guard to only apply in multiple mode, allowing single-file replacement - Note: test-file-input.html import issue is moot as file was removed in earlier commit Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
These files were accidentally included and are not part of the ct-file-input/ct-image-input PDF support feature: - 76 playwright screenshot files - 8 bug report and documentation files - 11 test pattern files - package.json/package-lock.json (playwright test dependency) This reduces the PR from 6,656 lines to just the actual feature code. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
3f75046 to
cbff650
Compare
Summary
This PR adds PDF support to the UI component library by introducing a new
ct-file-inputbase component and refactoringct-image-inputto extend it. This enables users to upload PDFs and other document types without inappropriate image compression attempts.Motivation
Currently, users need to upload PDFs (e.g., recipe PDFs in community-patterns), but
ct-image-inputonly handles images. We needed a generic file upload component that:Architecture
Key Design Decisions
ct-image-inputnow extendsct-file-inputrather than duplicating file handling logicprocessFile(),renderPreview(),shouldCompressFile()as protected methods for subclassesct-file-inputChanges
1. New
ct-file-inputComponent (cebf3a3)accept="application/pdf"oraccept="image/*,application/pdf"packages/ui/src/v2/components/ct-file-input/2. Refactor
ct-image-inputto Extend Base (dcd6123)Reduced code by 266 lines (from 512 to 246 lines)
CTFileInputinstead ofBaseElementcompressFile()overrideprocessFile()overriderenderFileInput()overrideimagesproperty (aliased tofiles)maxImagesproperty (aliased tomaxFiles)imagesandfiles3. JSX Type Definitions (cf6dea3)
CTFileInputElementinterfaceCTFileInputAttributeswith all propertiesIntrinsicElementsentry for type-safe JSX4. Test Files (23c0cab)
test-file-input.html- Browser-based teststest-file-upload.tsx- Pattern-based testsUsage
Upload PDFs
Mixed Upload (Images + PDFs)
Images (Existing - Still Works!)
Testing
community-patterns-2/test-images/recipes/pdfs/Backward Compatibility
✅ Zero breaking changes - All existing
ct-image-inputusage continues to work✅ Event detail includes both
images(old) andfiles(new) properties✅
maxImagesproperty aliased tomaxFiles✅
imagesproperty aliased tofilesFuture Extensions
This architecture makes it easy to add more file-type-specific components:
ct-audio-input- audio file uploads with waveform previewct-video-input- video uploads with thumbnail extractionct-document-input- Word/Excel with page count extraction🤖 Generated with Claude Code via Happy
Co-Authored-By: Claude noreply@anthropic.com
Co-Authored-By: Happy yesreply@happy.engineering
Summary by cubic
Adds a generic ct-file-input component and refactors ct-image-input to extend it, enabling PDF and document uploads without image compression. Keeps existing image flows working and fixes file limit and size validation issues.
New Features
Bug Fixes
Written for commit cbff650. Summary will update automatically on new commits.