Compare commits
3
Commits
70695ff218
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd11ea8255 | ||
|
|
bc21643e8c | ||
|
|
1ed5856db0 |
+1
-1
@@ -47,7 +47,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
|
|||||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
--mount=type=bind,source=src/,target=src/ \
|
--mount=type=bind,source=src/,target=src/ \
|
||||||
uv sync --no-editable
|
uv sync --no-editable --refresh-package prompts
|
||||||
|
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,3 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "8765:8765"
|
- "8765:8765"
|
||||||
volumes:
|
|
||||||
- ./docs:/app/src/personal_mcp/docs
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
name: jsfiddle-page-layout
|
||||||
|
description: Create a responsive sample page layout for a user-supplied domain and return paste-ready HTML and CSS for JSFiddle.
|
||||||
|
x-personal-mcp:
|
||||||
|
id: jsfiddle-page-layout
|
||||||
|
version: 1.0.0
|
||||||
|
tags:
|
||||||
|
- frontend
|
||||||
|
- html
|
||||||
|
- css
|
||||||
|
- jsfiddle
|
||||||
|
- layout
|
||||||
|
- prototyping
|
||||||
|
- prompts
|
||||||
|
capabilities:
|
||||||
|
- resource://prompts/jsfiddle-page-layout/document
|
||||||
|
arguments:
|
||||||
|
domain:
|
||||||
|
title: Domain
|
||||||
|
description: The product, service, organization, or subject the sample page should represent, including its audience when known.
|
||||||
|
required: true
|
||||||
|
layout_brief:
|
||||||
|
title: Layout brief
|
||||||
|
description: Optional page type, required sections, content priorities, visual direction, or constraints.
|
||||||
|
required: false
|
||||||
|
---
|
||||||
|
|
||||||
|
# JSFiddle Page Layout
|
||||||
|
|
||||||
|
Create a polished sample page layout for the supplied domain. The result must run by pasting the markup and styles into the [JSFiddle](https://jsfiddle.net/) HTML and CSS panes.
|
||||||
|
|
||||||
|
## Inputs
|
||||||
|
|
||||||
|
1. `domain`: the product, service, organization, or subject represented by the page, including its intended audience when known
|
||||||
|
2. `layout_brief`: optional page type, required sections, content priorities, visual direction, or constraints
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Infer the page's primary purpose, audience, content hierarchy, and most important user action from the inputs.
|
||||||
|
2. If the domain does not provide enough information to choose a useful page type or primary action, ask one concise clarification question before generating code.
|
||||||
|
3. Choose a visual direction and information density appropriate to the domain. Build the usable page itself, not a marketing explanation of the page.
|
||||||
|
4. Write semantic HTML with realistic domain-specific sample content. Do not use placeholder text such as lorem ipsum.
|
||||||
|
5. Build the layout with modern CSS, using [CSS Grid](https://css-tricks.com/complete-guide-css-grid-layout/) for two-dimensional page structure and [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) for one-dimensional alignment where each fits naturally.
|
||||||
|
6. Make the page responsive at narrow mobile and desktop widths without horizontal overflow, overlapping content, or clipped text.
|
||||||
|
7. Keep the example self-contained. Use no JavaScript, build tools, external stylesheets, images, or icon libraries unless the layout brief explicitly requires them.
|
||||||
|
8. Include accessible landmarks, heading order, labels, focus styles, color contrast, and reduced-motion handling when animation is present.
|
||||||
|
9. Use CSS custom properties for the color, typography, spacing, border, and shadow system. Avoid generic framework styling and tailor the visual language to the domain.
|
||||||
|
|
||||||
|
## Output Contract
|
||||||
|
|
||||||
|
Return exactly two fenced code blocks in this order:
|
||||||
|
|
||||||
|
1. An `html` block containing only the content for JSFiddle's HTML pane.
|
||||||
|
2. A `css` block containing only the content for JSFiddle's CSS pane.
|
||||||
|
|
||||||
|
Do not include setup instructions, design commentary, JavaScript, or prose outside the two code blocks.
|
||||||
|
|
||||||
|
## Quality Rules
|
||||||
|
|
||||||
|
1. Prefer semantic elements such as `header`, `nav`, `main`, `section`, `article`, `aside`, and `footer` when they match the content.
|
||||||
|
2. Reserve large display type for a true hero or primary page title; keep operational interfaces compact and easy to scan.
|
||||||
|
3. Use cards only for repeated items or genuinely framed tools. Do not place cards inside cards.
|
||||||
|
4. Use stable responsive constraints for grids, controls, media, and navigation so dynamic content does not shift the layout unexpectedly.
|
||||||
|
5. Avoid decorative gradients, floating color blobs, excessive rounding, and one-note palettes unless they are explicitly appropriate to the domain.
|
||||||
|
6. Ensure controls look and behave like their purpose, with visible hover and keyboard-focus states.
|
||||||
|
7. Keep all visible copy relevant to the fictional domain rather than describing the mockup or its implementation.
|
||||||
@@ -139,14 +139,14 @@ This example shows the ownership boundaries. Adapt state storage and dependency
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from contextlib import AsyncExitStack, asynccontextmanager
|
from contextlib import AsyncExitStack, asynccontextmanager
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncGeneratorr
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
||||||
async with AsyncExitStack() as stack:
|
async with AsyncExitStack() as stack:
|
||||||
engine = create_async_engine(settings.database_url)
|
engine = create_async_engine(settings.database_url)
|
||||||
stack.push_async_callback(engine.dispose)
|
stack.push_async_callback(engine.dispose)
|
||||||
@@ -160,7 +160,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
||||||
async def get_session() -> AsyncIterator[AsyncSession]:
|
async def get_session() -> AsyncGenerator[AsyncSession]:
|
||||||
async with app.state.session_factory() as session:
|
async with app.state.session_factory() as session:
|
||||||
yield session
|
yield session
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ Resolve settings at the composition boundary and call `get_engine(settings.datab
|
|||||||
The lifespan context manager only connects the cached resource to FastAPI ownership:
|
The lifespan context manager only connects the cached resource to FastAPI ownership:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncGeneratorr
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
||||||
database_url = app.state.settings.database_url
|
database_url = app.state.settings.database_url
|
||||||
engine = get_engine(database_url)
|
engine = get_engine(database_url)
|
||||||
app.state.engine = engine
|
app.state.engine = engine
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ Otherwise, a later call can return a maker that still references the old engine
|
|||||||
A small [`asynccontextmanager`](https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager) can make repository methods composable. It borrows an existing session when supplied; otherwise it creates and closes one from a supplied factory:
|
A small [`asynccontextmanager`](https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager) can make repository methods composable. It borrows an existing session when supplied; otherwise it creates and closes one from a supplied factory:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncGeneratorr
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ async def session_scope(
|
|||||||
*,
|
*,
|
||||||
database_url: str,
|
database_url: str,
|
||||||
session: AsyncSession | None = None,
|
session: AsyncSession | None = None,
|
||||||
) -> AsyncIterator[AsyncSession]:
|
) -> AsyncGenerator[AsyncSession]:
|
||||||
if session is not None:
|
if session is not None:
|
||||||
yield session
|
yield session
|
||||||
return
|
return
|
||||||
@@ -172,7 +172,7 @@ async def transaction_scope(
|
|||||||
*,
|
*,
|
||||||
database_url: str,
|
database_url: str,
|
||||||
session: AsyncSession | None = None,
|
session: AsyncSession | None = None,
|
||||||
) -> AsyncIterator[AsyncSession]:
|
) -> AsyncGenerator[AsyncSession]:
|
||||||
if session is not None:
|
if session is not None:
|
||||||
if not session.in_transaction():
|
if not session.in_transaction():
|
||||||
raise RuntimeError("A supplied session must have an active transaction")
|
raise RuntimeError("A supplied session must have an active transaction")
|
||||||
@@ -277,7 +277,7 @@ This preserves atomicity without making repository objects hold mutable `AsyncSe
|
|||||||
## Canonical FastAPI Dependency Pattern
|
## Canonical FastAPI Dependency Pattern
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncGenerator
|
||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
@@ -294,7 +294,7 @@ def resolve_session_factory(request: Request) -> SessionFactory:
|
|||||||
|
|
||||||
async def get_db_session(
|
async def get_db_session(
|
||||||
session_factory: SessionFactory = Depends(resolve_session_factory),
|
session_factory: SessionFactory = Depends(resolve_session_factory),
|
||||||
) -> AsyncIterator[AsyncSession]:
|
) -> AsyncGenerator[AsyncSession]:
|
||||||
async with session_factory() as session:
|
async with session_factory() as session:
|
||||||
yield session
|
yield session
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ Use migrations to provision an integration database when migrations are part of
|
|||||||
For tests that exercise code which calls `commit()`, start an outer transaction on one test connection. Bind the test `AsyncSession` to that connection and use `join_transaction_mode="create_savepoint"`. SQLAlchemy documents this as its test-suite pattern: session commits resolve a SAVEPOINT while fixture teardown rolls back the outer transaction.
|
For tests that exercise code which calls `commit()`, start an outer transaction on one test connection. Bind the test `AsyncSession` to that connection and use `join_transaction_mode="create_savepoint"`. SQLAlchemy documents this as its test-suite pattern: session commits resolve a SAVEPOINT while fixture teardown rolls back the outer transaction.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncGeneratorr
|
||||||
|
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||||
@@ -50,7 +50,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
|||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def session(test_engine: AsyncEngine) -> AsyncIterator[AsyncSession]:
|
async def session(test_engine: AsyncEngine) -> AsyncGenerator[AsyncSession]:
|
||||||
async with test_engine.connect() as connection:
|
async with test_engine.connect() as connection:
|
||||||
transaction = await connection.begin()
|
transaction = await connection.begin()
|
||||||
test_session = AsyncSession(
|
test_session = AsyncSession(
|
||||||
@@ -78,7 +78,7 @@ def app_with_test_session(
|
|||||||
app: FastAPI,
|
app: FastAPI,
|
||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
) -> FastAPI:
|
) -> FastAPI:
|
||||||
async def get_test_session() -> AsyncIterator[AsyncSession]:
|
async def get_test_session() -> AsyncGenerator[AsyncSession]:
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
app.dependency_overrides[get_session] = get_test_session
|
app.dependency_overrides[get_session] = get_test_session
|
||||||
@@ -104,7 +104,7 @@ from sqlalchemy.ext.asyncio import AsyncEngine
|
|||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def test_engine() -> AsyncIterator[AsyncEngine]:
|
async def test_engine() -> AsyncGenerator[AsyncEngine]:
|
||||||
engine, _ = create_database("sqlite+aiosqlite://")
|
engine, _ = create_database("sqlite+aiosqlite://")
|
||||||
async with engine.begin() as connection:
|
async with engine.begin() as connection:
|
||||||
await connection.run_sync(SQLModel.metadata.create_all)
|
await connection.run_sync(SQLModel.metadata.create_all)
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ def get_settings() -> Settings:
|
|||||||
The argument-free [`functools.cache`](https://docs.python.org/3/library/functools.html#functools.cache) provider is appropriate here because both the project entry point and Uvicorn's zero-argument factory need process-lifetime access. Each reload or worker process gets its own settings instance. Do not add override arguments to `get_settings()`; inject a `Settings` instance directly into `create_app()` in tests or alternate composition roots. See the [Pydantic settings implementation guide](../../pydantic-settings/SKILL.md) for source precedence, independent settings boundaries, cache clearing, and runtime reload guidance.
|
The argument-free [`functools.cache`](https://docs.python.org/3/library/functools.html#functools.cache) provider is appropriate here because both the project entry point and Uvicorn's zero-argument factory need process-lifetime access. Each reload or worker process gets its own settings instance. Do not add override arguments to `get_settings()`; inject a `Settings` instance directly into `create_app()` in tests or alternate composition roots. See the [Pydantic settings implementation guide](../../pydantic-settings/SKILL.md) for source precedence, independent settings boundaries, cache clearing, and runtime reload guidance.
|
||||||
|
|
||||||
```python title="src/my_app/main.py"
|
```python title="src/my_app/main.py"
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncGeneratorr
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
@@ -137,7 +137,7 @@ from my_app.config import Settings, get_settings
|
|||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
|
||||||
app.state.ready = True
|
app.state.ready = True
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncGenerator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -14,7 +14,7 @@ from personal_mcp.web.app import create_app
|
|||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture
|
@pytest_asyncio.fixture
|
||||||
async def client() -> AsyncIterator[AsyncClient]:
|
async def client() -> AsyncGenerator[AsyncClient]:
|
||||||
"""Provides an AsyncClient bound to a fresh application instance."""
|
"""Provides an AsyncClient bound to a fresh application instance."""
|
||||||
app = create_app()
|
app = create_app()
|
||||||
async with AsyncClient(
|
async with AsyncClient(
|
||||||
@@ -30,7 +30,7 @@ def mcp_session_factory():
|
|||||||
"""Provides an in-process context manager factory for MCP SDK sessions."""
|
"""Provides an in-process context manager factory for MCP SDK sessions."""
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def create_session(*, initialize: bool = True) -> AsyncIterator[ClientSession]:
|
async def create_session(*, initialize: bool = True) -> AsyncGenerator[ClientSession]:
|
||||||
app = create_app()
|
app = create_app()
|
||||||
mcp_url = f"http://testserver{app.state.settings.mounts.mcp}"
|
mcp_url = f"http://testserver{app.state.settings.mounts.mcp}"
|
||||||
async with (
|
async with (
|
||||||
|
|||||||
Reference in New Issue
Block a user