Skip to content

Feature/user style management#120

Draft
ShotaroKataoka wants to merge 34 commits intomainfrom
feature/user-style-management
Draft

Feature/user style management#120
ShotaroKataoka wants to merge 34 commits intomainfrom
feature/user-style-management

Conversation

@ShotaroKataoka
Copy link
Copy Markdown
Contributor

@ShotaroKataoka ShotaroKataoka commented May 6, 2026

User Style Management

Add user-created style support with pin (favorites), gallery UI, /styles page, and AI-powered style creation.

What changed

Pin & Gallery (Phase 1)

  • Pin (★) toggle on gallery cards and preview header
  • Pinned section + collapsible All Styles when pins exist
  • list_styles API extended with include_all param, returns pinned/source metadata
  • Engine filter_styles() pure function shared across MCP Local and MCP Server

User Styles & /styles Page (Phase 2)

  • New top-level /styles page (Decks | Styles nav tabs)
  • Save, delete, import (HTML upload), export (download) user styles
  • Hash routing: #create, #{name} for preview/edit
  • "Copy to My Styles" for builtin styles
  • Cloud: S3 user-styles/{user_id}/ + DynamoDB STYLE_PINS
  • Local: ~/.config/sdpm/styles/ + state.json

Style Creator Agent (Phase 3)

  • run_style_python tool — sandboxed Python execution for style HTML generation
  • Local: read_style(name) / write_style(name, html) sandbox helpers
  • Cloud: Code Interpreter with style.html + ref/{name}.html workspace
  • useChatStream hook + ChatInput component extracted from ChatPanel (shared)
  • StyleChatPanel + StyleChatShell — dedicated chat UI for style creation
  • ACP agent definition (sdpm-style.json) + role prompt
  • Cloud mode style_creator with _STYLE_TOOLS allowlist
  • Tool filtering via allowed_tools in ModeConfig → MCPClient tool_filters

Bug fixes in this PR

  • apply_style now searches user styles (Local + Cloud)
  • _extract_cover_html handles slides with extra CSS classes (slide slide--dark)
  • MODE_TO_AGENT key fixed (style_creator not style)

Testing

  • Local: pin toggle persists across reload
  • Local: style creation via agent chat (write_style → live preview)
  • Local: import/export/delete user styles
  • Unit tests: test_styles_resolution.py (141 tests pass)
  • Cloud: end-to-end

Out of scope

  • Style sharing between users
  • GUI-based style editor (color picker etc.)
  • Style versioning

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
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.

2 participants