Skip to content

User Style & Template Management#120

Open
ShotaroKataoka wants to merge 49 commits intomainfrom
feature/user-style-management
Open

User Style & Template Management#120
ShotaroKataoka wants to merge 49 commits intomainfrom
feature/user-style-management

Conversation

@ShotaroKataoka
Copy link
Copy Markdown
Contributor

@ShotaroKataoka ShotaroKataoka commented May 6, 2026

close: #63

Summary

Adds a full user style and template management system — from gallery browsing and
pinning, to AI-assisted style editing via chat, to cloud-backed template upload/
analysis.

What's included

User Style Management (core feature)

  • /styles page with gallery, pin/filter, section layout
  • Save/delete/import/export styles with My Styles vs All Styles sections
  • Style chat panel with AI agent (run_style_python sandbox)
  • "Edit with AI" button, prompt chips, file drop zone for color analysis
  • Hash routing for deep-linking into style editor
  • Cloud layer: S3-backed style storage + agent integration

User Template Management

  • Engine API + MCP Local + WebUI for template upload/analyze/delete/rename
  • Cloud layer: S3 source of truth for builtin templates, proxy with inline CORS
  • Template metadata caching in state.json

REST → HTTP API Migration

  • Migrated REST API to HTTP API (v2 event format)
  • Fixed CORS preflight, trailing slash, get_user_id for v2 payload

UX & Infra Polish

  • Shared ConfirmDialog component replacing 4 inline modals
  • Shared StyleSlidePreview component
  • Header tab navigation
  • ChatPanel internal refactor for reuse across deck/style contexts
  • Path traversal guards, execFileSync for command injection prevention (ASH/CodeQL
    )
  • Unused import cleanup (ruff F401)

Files changed

67 files changed, +5634 / −1567

Testing

  • TypeScript: tsc --noEmit passes
  • Lint: ruff check passes
  • Security: ASH scan findings resolved (command injection → execFileSync, path
    traversal → containment checks)
  • Local mode: style CRUD, template upload/rename/delete, chat agent tested end-to-
    end
  • Cloud mode: S3 template operations, HTTP API CORS verified

SPEC: 20260503-0854_user-style-management
Progress: Phase 1a complete
- StyleCard: ★ pin button (hover fade-in, bounce animation, stopPropagation)
- Pinned section + All Styles collapsible when pins exist
- Flat grid when no pins (backward compatible)
- Preview header: ★ pin toggle next to style name
- Custom badge for user styles
- Scroll position preserved on pin toggle
- StyleEntry type: added pinned/source fields with backfill defaults
Next: Phase 1b — Engine + API layer
…ucture

SPEC: 20260503-0854_user-style-management
Progress: Phase 1b complete
- config.py: get_state() / update_state() for state.json
- reference/__init__.py: filter_styles() pure function (I/O-free)
- api.py: list_styles_filtered() filesystem entry point
- tools.py: list_styles with include_all, browser open removed
- server.py: include_all parameter on MCP tool
- server_acp.py: fixed to use tools.py (user-local styles now visible)
- 21 tests passing (9 new)
Next: Phase 1c — Local API Routes + frontend API connection
…tion

SPEC: 20260503-0854_user-style-management
Progress: Phase 1c (Local portion) complete
- sdpmPaths.ts: shared helpers for config dir, state.json, style listing
- GET /api/styles: user-local + bundled with pinned/source metadata
- GET /api/styles/[name]: search user-local first, then bundled
- POST /api/styles/pin: pin toggle persisted to state.json
- deckService.ts: pinStyle() API function
- SpecStepNav: optimistic pin toggle connected to API
Next: Phase 1c — Cloud layer (DynamoDB pins, MCP Server)
SPEC: 20260503-0854_user-style-management
Progress: Phase 2 — /styles page + AppShell tabs
- AppShell: Decks | Styles tab navigation (active state, hidden in workspace)
- /styles page: user styles (top, with delete) + built-in styles grid
- Style preview with iframe scaling (1920×1080)
- Pin toggle on cards and preview
- Custom badge for user styles
Next: Phase 2 — Engine save/delete + API routes
…s page polish

SPEC: 20260503-0854_user-style-management
- StyleSlidePreview: shared component for style/art-direction rendering
  - Resets body zoom/padding/margin, preserves slide border/outline
  - 2200px iframe width to accommodate decorative borders
  - 8px gap between slides
- /styles page: full-width preview, dynamic card scaling
- SpecStepNav: art-direction result now uses StyleSlidePreview
- sdpmPaths: cover extraction uses div.slide, resets body zoom
- Removed duplicate StylePreviewInline and inline iframe code
Change StyleCard from <button> to <div role=button> to allow
nested pin <button> without violating HTML spec.
…UX polish

SPEC: 20260503-0854_user-style-management
Phase 2 + Phase 4 (pulled forward):

API Routes:
- POST /api/styles/user — save user style (title tag validation)
- DELETE /api/styles/user/[name] — delete user style

Service layer:
- saveUserStyle(), deleteUserStyle() in deckService.ts

/styles page:
- Create with AI card (primary) + Import Style link (secondary)
- Delete with confirmation dialog (Esc, aria-modal, autoFocus)
- Export (HTML download) on hover + preview header
- Toast notifications replacing alert() for import/delete feedback
- Touch target fix on Import Style link

Bug fix:
- Slide class regex: class="slide" missed variants like
  slide--dark, slide-alt. Changed to class="slide[\s"]
  Fixes corporate-executive (4→6) and cute-playful (2→4)
…tyles default open

SPEC: 20260503-0854_user-style-management
Progress: Phase 2.5 + Phase 2.7 complete
- apply_style: use _find_style_in_dirs for user style support
- /styles page: migrate to useStyleWorkspace hash routing
- Add Copy to My Styles for builtin styles
- All Styles collapsible default open (UX: Anticipatory Design)
…onents

SPEC: 20260503-0854_user-style-management
Progress: Phase 3a (partial) + Phase 3b complete

New shared components:
- useChatStream hook: streaming state management core
- ChatInput: reusable input with PlusMenu, attachments, IME
- useStyleWorkspace: hash routing for /styles page

Style chat UI:
- StyleChatPanel: lightweight orchestrator using shared components
- StyleChatShell: resizable side panel (mirrors ChatPanelShell UX)
- /styles page: #create → Untitled Style + chat open (Notion pattern)
- User style preview: 'Edit with AI' button opens chat panel
- Live preview via onStyleHtmlUpdate (tool result → iframe)
- MODE_TO_AGENT: style → sdpm-style
… + agent definition

SPEC: 20260503-0854_user-style-management
Progress: Phase 3c (sandbox + tool) and Phase 3d (agent def + prompt + save callback) complete
- sandbox.py: make_style_runner() with read_style helper
- server_acp.py: run_style_python tool (workspace, save, live preview)
- sdpm-style.json: ACP agent definition
- style-creator.md: agent prompt
- StyleChatPanel: onStyleSaved callback for toast + refresh
Next: Manual integration test with WebUI
Root cause: ChatInput's FileDropZone wrapper had h-full, expanding to
fill parent flex container and pushing messages area to zero height.
Fix: Add className prop to FileDropZone, pass 'relative' (no h-full)
from ChatInput. Existing callers use default 'relative h-full'.
SPEC: 20260505-1330_style-chat-ux-polish
- R1: Elevate Edit with AI to primary CTA (brand-teal, Sparkles icon)
- R2: Add prompt chips to welcome screen for cognitive load reduction
- R3: Header already shows styleName (no change needed)
…aming/rename UI

- sandbox: remove workspace (tempdir), expose read_style/write_style only
- server_acp: remove style_id/save params, direct write to user styles dir
- StyleChatPanel: inject [Style: name] on first message for agent context
- /styles page: name input dialog for create, ⋯ menu with rename/export/delete
- API: PATCH /styles/user/[name] for rename (with pin reference update)
- Preview header: ★ + Edit with AI + ⋯ menu (clean layout)
- Card: ★ only on hover, ⋯ menu fixed-position (no overflow clip)
- useStyleWorkspace: refreshPreview() for post-write refetch
- style-creator.md: updated prompt for new write_style(name, html) API

SPEC: 20260503-0854_user-style-management
Progress: Phase 3c refactored, naming/rename UI added
…lity hints

Replace prompt hint buttons and plain text with icon + short phrase
layout showing agent capabilities (describe, drop image, customize).
Follows UX Excellence Instant Clarity principle.
…is to agent directly

- upload_file() now returns filePath (absolute) and colorAnalysis for images
- [Attached:] marker uses path format in Local mode (quoted for spaces)
- Remove read_uploaded_file MCP tool from ACP (agent uses read tool directly)
- Color analysis: PIL quantize (5 colors), brightness, saturation (~8ms)
- Fix uploadService.ts to propagate filePath/colorAnalysis from API response

SPEC: 20260506-1033_acp-upload-path-direct-access
…n env unset

Path('') evaluates to PosixPath('.') which is truthy, so the 'or'
fallback to ~/Documents/SDPM-Presentations never triggered.
This caused .sessions/ to be created in cwd (mcp-local/) instead.

Fix: check the string value before constructing Path.
- Create with AI: auto-generate style-{YYYYMMDD-HHMM} name (no dialog)
- Inline rename: click name in card list to edit in-place
- Inline validation errors (edit mode stays open on error)
- Pencil icon affordance on name hover
- Replace ⋯ dropdown with direct icon buttons (★ Export Delete)
- Remove <title> tag validation (sandbox, API, import)

SPEC: 20260503-0854_user-style-management
- Extract shared logic into useChatStream hook and ChatInput component
- Remove slidePreviewUrls prop (unused after refactor)
- Remove onSlideClick handler (moved to different interaction)
- Add ModelSelector component
- Add attachmentMarker utility
- StyleChatPanel/Shell minor adjustments

SPEC: 20260503-0854_user-style-management
ChatInput extraction moved FileDropZone to input-only scope.
Restore panel-wide drop target by wrapping ChatPanel and
StyleChatPanel roots with FileDropZone, delegating to
ChatInput.addFiles() via imperative handle.

SPEC: 20260503-0854_user-style-management
SPEC: 20260503-0854_user-style-management
Phase: 1d, 2 Cloud, 2.5 Cloud, 3e

- Storage: get_style_pins/put_style_pins (DynamoDB PK=USER#, SK=STYLE_PINS)
- list_styles: user styles (S3 user-styles/{user_id}/) + pins + filter_styles
- apply_style: search user styles before builtin
- run_style_python: Code Interpreter sandbox with style.html + ref/ workspace
- API Gateway: POST /styles/pin, POST /styles/user, DELETE/PATCH /styles/user/{name}
- api/index.py: 6 endpoints (GET×2 revised, 4 new)
- Agent: MODES["style_creator"] with allowed_tools filtering via MCPClient tool_filters
- Tool isolation: _DECK_TOOLS (19) / _STYLE_TOOLS (4) allowlists
- Local agents: explicit allowlist replacing @sdpm/* wildcard
- Add setAgentConfig call (was missing, causing 'ARN not configured')
- Fix mode: 'style' → 'style_creator' to match MODES key
- _DECK_TOOLS: add list_asset_sources, list_templates, search_assets; remove non-MCP hearing
- _STYLE_TOOLS: add read_uploaded_file, remove hearing
- upload.py: add _analyze_colors() for image color palette extraction
StyleChatPanel sends mode='style_creator' but MODE_TO_AGENT had 'style',
causing Local mode to fall back to sdpm-spec agent instead of sdpm-style.
…h extra classes

_extract_cover_html used exact match '<div class="slide">' which missed
slides with additional classes like 'slide slide--dark'. Now uses regex
pattern matching '<div class="slide[\s"]' consistent with Local version.
@ShotaroKataoka ShotaroKataoka added the blog:pending ブログ記事にする label May 6, 2026
Comment thread web-ui/src/app/api/styles/user/route.ts Fixed
…nagement

# Conflicts:
#	web-ui/src/components/chat/ChatPanel.tsx
…atch block

Port PR#118 error classification to useChatStream.ts where streaming
logic now lives in this branch.
Defense-in-depth: validate resolved path stays within styles dir.
The regex check already prevents traversal, but realpath check
satisfies CodeQL's taint tracking.
- ChatInput: move handleFilesRef assignment into useEffect
- useStyleWorkspace: replace useRef with useState lazy initializer
@ShotaroKataoka ShotaroKataoka marked this pull request as draft May 6, 2026 13:53
…CP Local, WebUI

SPEC: 20260506-2312_user-template-management
Phase 1: Engine API (list_templates_with_metadata, analyze_and_store_template)
  - Extract _extract_theme_colors_raw to analyzer (decouple from builder)
  - MCP Local list_templates returns metadata from state.json
Phase 2: WebUI /templates page (Local version)
  - API Routes: GET list, GET download, POST upload, PATCH rename/description, DELETE
  - Template cards with theme preview strip, color palette, fonts, layout count
  - Upload dialog with drag-and-drop, description input, analysis spinner
  - Inline editable name (with lint) and description for user templates
  - AppShell: Decks | Styles | Templates navigation
Next: Phase 3 (Cloud layer — S3 + DDB + MCP Server)
- Persist analyzed metadata for builtin and user templates
- Skip Python analysis on subsequent requests (read from cache)
- Builtin cached as 'builtin:{name}', user as '{name}'

SPEC: 20260506-2312_user-template-management
- Delete dialog: opaque bg, backdrop-blur, entry animation, deeper shadow
- Preview more-menu: opaque bg, backdrop-blur, larger touch targets

SPEC: 20260506-2312_user-template-management
- Storage ABC: 7 user template CRUD methods (list/get/put/delete/download/rename/update)
- AwsStorage: DDB (USER#{user_id}/TEMPLATE#{name}) + S3 (user-templates/{user_id}/) impl
- MCP Server: list_templates + analyze_template now accept user_id, search user templates
- API Lambda: GET/POST/DELETE/PATCH /templates endpoints with multipart upload + analysis
- api/requirements.txt: add python-pptx + lxml for in-Lambda template analysis
- CDK: API Gateway routes + Lambda bundling includes sdpm/analyzer module

SPEC: 20260506-2312_user-template-management
…olicy size limit

API Gateway individual method permissions exceeded the 20KB Lambda
resource-based policy limit. Replace 5 individual routes with a single
proxy resource (/templates/{proxy+}) that requires only 2 permissions.

SPEC: 20260506-2312_user-template-management
Comment thread web-ui/src/app/api/templates/user/route.ts Fixed
Comment thread web-ui/src/app/api/templates/user/route.ts Fixed
- Replace proxy resource with explicit resources (CORS preflight needs
  individual resources for defaultCorsPreflightOptions to add OPTIONS)
- Use ANY method instead of individual GET/POST/DELETE/PATCH to reduce
  Lambda Permission count (policy size limit was 20KB)
- Convert styles routes to ANY as well to free up policy space

SPEC: 20260506-2312_user-template-management
…ates

Previous deploy left {proxy+} in API Gateway state, causing conflict
with {name} sibling resource. Use addProxy with defaultCorsPreflightOptions
to handle CORS at the proxy level (not relying on RestApi-level defaults
which don't apply to proxy children).

SPEC: 20260506-2312_user-template-management
… templates

- API Lambda: list_objects for existence, DDB as ETag-validated cache,
  lazy analysis on cache miss
- MCP Server storage: list_templates uses S3 list_objects + DDB metadata
- CDK: remove CustomResource for template DDB registration (no longer needed)
- download_template: direct S3 head_object instead of DDB lookup

SPEC: 20260506-2312_user-template-management
- Replace API Gateway REST API with HTTP API ( catch-all route)
- Lambda Permission 20KB policy limit structurally eliminated (IAM role)
- APIGatewayRestResolver → APIGatewayHttpResolver (Powertools)
- CognitoUserPoolsAuthorizer → HttpJwtAuthorizer
- get_user_id/get_user_alias: support both v1 and v2 event formats
- WAF association updated to HTTP API stage ARN
- All route definitions removed from CDK (Powertools handles routing)
- CORS: declarative at API level (no per-resource OPTIONS)

SPEC: 20260507-0809_rest-to-http-api-migration
httpApi.apiEndpoint returns URL without trailing slash (unlike REST API's
api.url which included /prod/). Frontend concatenates paths directly,
causing ERR_NAME_NOT_RESOLVED.

SPEC: 20260507-0809_rest-to-http-api-migration
HTTP API's JWT authorizer intercepts OPTIONS requests before CORS
preflight handling. Add explicit OPTIONS route with HttpNoneAuthorizer
to bypass JWT validation for preflight requests.

Also enumerate CORS methods explicitly and add maxAge.

SPEC: 20260507-0809_rest-to-http-api-migration
@ShotaroKataoka ShotaroKataoka force-pushed the feature/user-style-management branch from 334e020 to 07ef340 Compare May 6, 2026 23:45
Use raw_event dict access instead of Powertools wrapper objects to
reliably extract JWT claims from HTTP API v2 authorizer format:
  v2: requestContext.authorizer.jwt.claims.sub
  v1: requestContext.authorizer.claims.sub

SPEC: 20260507-0809_rest-to-http-api-migration
Match styles page pattern: bg-black/70 + backdrop-blur-sm for
consistent modal backdrop across the app.

SPEC: 20260506-2312_user-template-management
… inline modals

Radix AlertDialog-based ConfirmDialog with Notion/Slack quality:
- Focus trap, aria-modal, Escape handling (Radix built-in)
- Spring zoom + fade animation (in/out)
- 3-layer shadow for depth
- destructive/default variants
- CSS custom property-ready for future light mode

Replaced: DeleteDeckModal, styles delete, templates delete, DeckActions publish.
Deleted: DeleteDeckModal.tsx (obsolete).

SPEC: 20260507-0927_confirm-dialog-component
…ty findings

- Remove unused PPTXBuilder import (ruff F401)
- Replace execSync with execFileSync + sys.argv to eliminate command injection
  in templates/route.ts and templates/user/route.ts
- Add realpath + startsWith containment check for path traversal
  in templates/user/[name]/route.ts
- Remove manual string escaping that caused CodeQL double-escape warning
@ShotaroKataoka ShotaroKataoka changed the title Feature/user style management User Style & Template Management May 7, 2026
… ASH findings

- templates/[name]/route.ts: validate basename + startsWith containment
- templates/user/[name]/route.ts: add nosemgrep for guarded path.join
…yload limit

- Add POST /templates/user/upload-url for presigned S3 PUT URL
- Change POST /templates/user to accept JSON body (name, description)
  and read .pptx from S3 for analysis
- Update frontend uploadTemplate() to 3-step flow:
  presign → S3 PUT → register+analyze
- Eliminates Lambda 6MB payload limit for template uploads
…al findings

basename validation + startsWith containment already guard these paths.
@ShotaroKataoka ShotaroKataoka marked this pull request as ready for review May 7, 2026 01:02
API returns {downloadUrl} JSON but frontend was saving the JSON body
as a .pptx blob, producing a corrupted file. Now fetches the actual
binary from the presigned URL before saving.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

blog:pending ブログ記事にする

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[WebUI] 新規スタイル作成機能の追加

2 participants