diff --git a/agent/core/knowledge_enricher.py b/agent/core/knowledge_enricher.py
new file mode 100644
index 00000000..80ae3818
--- /dev/null
+++ b/agent/core/knowledge_enricher.py
@@ -0,0 +1,201 @@
+from pathlib import Path
+import logging
+
+from llm.common import Message, TextRaw, Tool, ToolUse, Completion
+from llm.utils import get_universal_llm_client
+
+logger = logging.getLogger(__name__)
+
+
+class KnowledgeBaseEnricher:
+ """dynamic prompt enrichment based on knowledge base topics."""
+
+ _instances: dict[str, "KnowledgeBaseEnricher"] = {}
+
+ def __new__(cls, knowledge_base_dir: str | Path | None = None) -> "KnowledgeBaseEnricher":
+ # determine the canonical path for the knowledge base directory
+ if knowledge_base_dir:
+ kb_dir = Path(knowledge_base_dir).resolve()
+ else:
+ # fallback: look for knowledge_base relative to this file
+ current_dir = Path(__file__).parent
+ kb_dir = (current_dir / "knowledge_base").resolve()
+
+ # use the resolved path as the key
+ key = str(kb_dir)
+
+ if key not in cls._instances:
+ instance = super().__new__(cls)
+ cls._instances[key] = instance
+ instance._initialized = False
+
+ return cls._instances[key]
+
+ def __init__(self, knowledge_base_dir: str | Path | None = None):
+ # singleton pattern - only initialize once per directory
+ if self._initialized:
+ return
+
+ self.llm = get_universal_llm_client()
+ self.knowledge_base_dir = Path(knowledge_base_dir) if knowledge_base_dir else None
+ self.knowledge_base = self._load_knowledge_base()
+ self._select_topics_tool = self._create_selection_tool()
+ self._initialized = True
+
+ def _load_knowledge_base(self) -> dict[str, str]:
+ """load all .md files from knowledge_base directory as key-value pairs."""
+ knowledge_base = {}
+
+ # determine knowledge base directory
+ if self.knowledge_base_dir:
+ kb_dir = self.knowledge_base_dir
+ else:
+ # fallback: look for knowledge_base relative to this file
+ current_dir = Path(__file__).parent
+ kb_dir = current_dir / "knowledge_base"
+
+ if not kb_dir.exists():
+ raise FileNotFoundError(f"knowledge base directory not found: {kb_dir}")
+
+ # load all .md files
+ for md_file in kb_dir.glob("*.md"):
+ key = md_file.stem # filename without .md extension
+ try:
+ content = md_file.read_text(encoding="utf-8")
+ knowledge_base[key] = content
+ logger.debug(f"loaded knowledge topic: {key}")
+ except Exception as e:
+ logger.error(f"failed to load {md_file}: {e}")
+
+ logger.info(f"loaded {len(knowledge_base)} knowledge base topics")
+ return knowledge_base
+
+ def _create_selection_tool(self) -> Tool:
+ """create tool for LLM to select relevant knowledge topics."""
+ return {
+ "name": "select_knowledge_topics",
+ "description": "select relevant knowledge base topics for the given task",
+ "input_schema": {
+ "type": "object",
+ "properties": {
+ "topics": {
+ "type": "array",
+ "items": {"type": "string"},
+ "description": "list of topic keys to include in the system prompt"
+ }
+ },
+ "required": ["topics"]
+ }
+ }
+
+ def _get_phase_description(self, phase: str) -> str:
+ """get human-readable description of development phase."""
+ phase_descriptions = {
+ # tRPC phases
+ "draft": "creating schemas, types, and database models",
+ "handler": "implementing API handlers and business logic",
+ "frontend": "building user interface components and interactions",
+ "edit": "modifying existing code based on feedback",
+ # NiceGUI phases
+ "data_model": "designing SQLModel data structures and database schemas",
+ "application": "building UI components and application logic with NiceGUI",
+ # Generic fallback
+ "default": "general development phase"
+ }
+ return phase_descriptions.get(phase, f"development phase: {phase}")
+
+ async def enrich_prompt(self, user_prompt: str, development_phase: str | None = None) -> str:
+ """select relevant knowledge topics and return concatenated content."""
+ if not self.knowledge_base:
+ logger.warning("no knowledge base topics available")
+ return ""
+
+ available_topics = list(self.knowledge_base.keys())
+
+ # build context message including development phase if provided
+ context_parts = []
+ if development_phase:
+ phase_description = self._get_phase_description(development_phase)
+ context_parts.append(f"current development phase: {development_phase} ({phase_description})")
+ context_parts.extend([
+ f"user task: {user_prompt}",
+ f"available knowledge topics: {available_topics}",
+ "",
+ "select only the relevant topics needed for this specific development phase and task. "
+ "prioritize topics that provide guidance for the current phase of development. "
+ "focus on topics that are directly applicable to what needs to be implemented right now."
+ ])
+
+ # create message asking LLM to select relevant topics
+ messages = [Message(
+ role="user",
+ content=[TextRaw("\n\n".join(context_parts))]
+ )]
+
+ try:
+ response = await self.llm.completion(
+ messages=messages,
+ max_tokens=1000,
+ tools=[self._select_topics_tool],
+ tool_choice="auto"
+ )
+
+ selected_topics = self._extract_selected_topics(response)
+ enrichment = self._build_system_prompt(selected_topics)
+
+ # log selection and size info
+ logger.info(f"selected {len(selected_topics)} topics from {len(available_topics)} available: {selected_topics}")
+ if enrichment:
+ char_count = len(enrichment)
+ line_count = enrichment.count('\n') + 1
+ logger.info(f"enrichment added: {char_count} characters, {line_count} lines")
+ else:
+ logger.info("no enrichment added (no topics selected)")
+
+ return enrichment
+
+ except Exception as e:
+ logger.error(f"failed to get topic selection from LLM: {e}")
+ # fallback to empty enrichment
+ return ""
+
+ def _extract_selected_topics(self, response: Completion) -> list[str]:
+ """extract selected topic keys from LLM tool call response."""
+ selected_topics = []
+
+ for content_block in response.content:
+ if isinstance(content_block, ToolUse) and content_block.name == "select_knowledge_topics":
+ tool_input = content_block.input
+ if isinstance(tool_input, dict) and "topics" in tool_input:
+ topics = tool_input["topics"]
+ if isinstance(topics, list):
+ selected_topics.extend([str(topic) for topic in topics])
+
+ # filter out invalid topic keys
+ valid_topics = [topic for topic in selected_topics if topic in self.knowledge_base]
+
+ if len(valid_topics) != len(selected_topics):
+ invalid = [topic for topic in selected_topics if topic not in self.knowledge_base]
+ logger.warning(f"invalid topics requested: {invalid}")
+
+ return valid_topics
+
+ def _build_system_prompt(self, topic_keys: list[str]) -> str:
+ """concatenate selected topics into system prompt section."""
+ if not topic_keys:
+ return ""
+
+ sections = []
+ sections.append("# relevant knowledge base:")
+
+ for key in topic_keys:
+ if key in self.knowledge_base:
+ content = self.knowledge_base[key].strip()
+ sections.append(f"## {key}")
+ sections.append(content)
+
+ return "\n\n".join(sections)
+
+ def get_available_topics(self) -> list[str]:
+ """return list of available knowledge base topic keys."""
+ return list(self.knowledge_base.keys())
diff --git a/agent/nicegui_agent/actors.py b/agent/nicegui_agent/actors.py
index fa7c1fec..ae9bca0c 100644
--- a/agent/nicegui_agent/actors.py
+++ b/agent/nicegui_agent/actors.py
@@ -1,6 +1,7 @@
import jinja2
import logging
import anyio
+from pathlib import Path
from typing import Callable, Awaitable
from core.base_node import Node
from core.workspace import Workspace
@@ -9,6 +10,7 @@
from nicegui_agent import playbooks
from core.notification_utils import notify_if_callback, notify_stage
from integrations.dbrx import DatabricksClient
+from core.knowledge_enricher import KnowledgeBaseEnricher
logger = logging.getLogger(__name__)
@@ -47,6 +49,44 @@ def __init__(
]
self.files_allowed = files_allowed or ["app/", "tests/"]
+ # Knowledge base enricher with nicegui-specific knowledge base (singleton)
+ nicegui_kb_dir = Path(__file__).parent / "knowledge_base"
+ self.enricher = KnowledgeBaseEnricher(nicegui_kb_dir)
+
+ def _determine_development_phase(self, system_prompt: str) -> str:
+ """determine development phase from system prompt."""
+ prompt_lower = system_prompt.lower()
+ if ("data modeling" in prompt_lower or
+ ("sqlmodel" in prompt_lower and "data structures" in prompt_lower) or
+ "database schemas" in prompt_lower):
+ return "data_model"
+ elif ("application development" in prompt_lower or
+ "ui components" in prompt_lower or
+ "application logic" in prompt_lower or
+ "existing data models" in prompt_lower):
+ return "application"
+ else:
+ return "default"
+
+ async def _enrich_system_prompt(self, base_system_prompt: str, user_prompt: str) -> str:
+ """enrich system prompt with relevant knowledge base topics."""
+ try:
+ original_size = len(base_system_prompt)
+ development_phase = self._determine_development_phase(base_system_prompt)
+ enrichment = await self.enricher.enrich_prompt(user_prompt, development_phase)
+
+ if enrichment:
+ enriched_prompt = f"{base_system_prompt}\n\n{enrichment}"
+ new_size = len(enriched_prompt)
+ logger.info(f"system prompt enriched: {original_size} → {new_size} chars (+{new_size - original_size})")
+ return enriched_prompt
+ else:
+ logger.info(f"no enrichment added, keeping original: {original_size} chars")
+ return base_system_prompt
+ except Exception as e:
+ logger.warning(f"failed to enrich system prompt: {e}")
+ return base_system_prompt
+
async def execute(
self,
files: dict[str, str],
@@ -90,6 +130,9 @@ async def execute(
message = Message(role="user", content=[TextRaw(user_prompt_rendered)])
self.root = Node(BaseData(workspace, [message], {}))
+ # Enrich system prompt with relevant knowledge base topics
+ enriched_system_prompt = await self._enrich_system_prompt(self.system_prompt, user_prompt)
+
solution: Node[BaseData] | None = None
iteration = 0
while solution is None:
@@ -112,7 +155,7 @@ async def execute(
)
nodes = await self.run_llm(
candidates,
- system_prompt=self.system_prompt,
+ system_prompt=enriched_system_prompt,
tools=self.tools,
max_tokens=8192,
)
diff --git a/agent/nicegui_agent/knowledge_base/app_async_sync_patterns.md b/agent/nicegui_agent/knowledge_base/app_async_sync_patterns.md
new file mode 100644
index 00000000..d4e0786c
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/app_async_sync_patterns.md
@@ -0,0 +1,7 @@
+# Async vs Sync Page Functions
+
+Use async page functions when you need to access `app.storage.tab` (requires `await ui.context.client.connected()`), show dialogs and wait for user response, or perform asynchronous operations like API calls and file I/O. The async pattern is necessary when your page needs to wait for external resources or user interactions.
+
+Use sync page functions for simple UI rendering without async operations, basic event handlers, and state updates. Sync functions are more straightforward and perform better when you don't need to await anything. Most basic pages with forms, navigation, and timers can use sync functions.
+
+Choose the right pattern based on your needs: async for tab storage, dialogs, file uploads with processing; sync for simple forms, navigation, timers, and basic UI updates. Don't make pages async unless you actually need to await something, as it adds unnecessary complexity.
diff --git a/agent/nicegui_agent/knowledge_base/app_error_handling.md b/agent/nicegui_agent/knowledge_base/app_error_handling.md
new file mode 100644
index 00000000..6efdf5d5
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/app_error_handling.md
@@ -0,0 +1,7 @@
+# Error Handling and User Feedback
+
+Use try/except blocks for operations that might fail and provide immediate user feedback through `ui.notify()`. Always log errors with appropriate detail for debugging while showing user-friendly messages. Use `ui.notify('File processed successfully!', type='positive')` for success and `ui.notify(f'Error: {str(e)}', type='negative')` for failures.
+
+Never use quiet failures or generic exception handling that hides important errors. Always log the specific error context: `logger.info(f'Error processing file: {filename}')` before showing user notifications. This dual approach ensures both user experience and debugging capability.
+
+Provide contextual feedback for different operation types: `type='positive'` for successful operations, `type='negative'` for errors, `type='warning'` for cautionary messages. Keep error messages concise but informative, avoiding technical jargon that users won't understand while maintaining enough detail for troubleshooting.
diff --git a/agent/nicegui_agent/knowledge_base/app_modularity.md b/agent/nicegui_agent/knowledge_base/app_modularity.md
new file mode 100644
index 00000000..ea67f807
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/app_modularity.md
@@ -0,0 +1,7 @@
+# Application Modularity
+
+Break your application into focused modules that narrow their scope and separate core logic from view components. Each module should be defined in a separate file and expose a `create()` function that assembles the module's UI. This pattern promotes code organization and reusability across your application.
+
+Define modules with clear boundaries: create functions like `word_counter.create()` that set up routes and UI components for specific features. Keep the module's logic self-contained and avoid cross-module dependencies where possible. Each module should handle its own UI setup and event handlers.
+
+Build your root application in `app/startup.py` by importing and calling each module's create function. Always call `create_tables()` first to ensure database schema exists, then initialize each module: `word_counter.create()`. This centralized startup pattern makes it easy to manage your application's initialization sequence.
diff --git a/agent/nicegui_agent/knowledge_base/app_timers_navigation.md b/agent/nicegui_agent/knowledge_base/app_timers_navigation.md
new file mode 100644
index 00000000..69ab6dbc
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/app_timers_navigation.md
@@ -0,0 +1,7 @@
+# Timers and Navigation Patterns
+
+Use `ui.timer` for periodic tasks and auto-refreshing content. Create update functions that modify existing UI elements rather than creating new ones: `time_label.set_text(f'Current time: {datetime.now().strftime("%H:%M:%S")}')`. Call the update function once initially, then set up the timer: `ui.timer(1.0, update_time)`.
+
+Implement navigation using `ui.link` for internal links and `ui.navigate.to()` for programmatic navigation. Use `ui.link('Go to Dashboard', '/dashboard')` for user-clickable navigation and `ui.navigate.to('/settings')` within event handlers for conditional or automated navigation.
+
+For dialogs and user interactions, use async patterns with proper awaiting: `result = await ui.dialog('Are you sure?', ['Yes', 'No'])`. Handle the result appropriately and provide feedback through notifications. This pattern works well for confirmation dialogs and complex user input scenarios.
diff --git a/agent/nicegui_agent/knowledge_base/components_common_pitfalls.md b/agent/nicegui_agent/knowledge_base/components_common_pitfalls.md
new file mode 100644
index 00000000..72a18651
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/components_common_pitfalls.md
@@ -0,0 +1,7 @@
+# Common NiceGUI Component Pitfalls
+
+Avoid passing both positional and keyword arguments for the same parameter. For `ui.date()`, never write `ui.date('Date', value=date.today())` as this causes "multiple values for argument 'value'". Instead use `ui.date(value=date.today())`. For date values, use `.isoformat()` when setting: `date_input.set_value(date.today().isoformat())`.
+
+Don't use non-existent parameters like `size` for `ui.button()`. Instead of `ui.button('Click', size='sm')`, use CSS classes: `ui.button('Click').classes('text-sm')`. Similarly, use proper dialog creation patterns: `with ui.dialog() as dialog, ui.card():` rather than trying to use async context managers.
+
+Capture nullable values safely in lambda functions: use `on_click=lambda user_id=user.id: delete_user(user_id) if user_id else None` instead of `on_click=lambda: delete_user(user.id)` where `user.id` might be None. Always register modules properly in startup.py by importing and calling their `create()` functions.
diff --git a/agent/nicegui_agent/knowledge_base/databricks_integration.md b/agent/nicegui_agent/knowledge_base/databricks_integration.md
new file mode 100644
index 00000000..e4d95667
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/databricks_integration.md
@@ -0,0 +1,7 @@
+# Databricks Integration Patterns
+
+Always check real table structure and data in Databricks before implementing models. Use the `DatabricksModel` base class with proper catalog, schema, and table class variables: `__catalog__ = "samples"`, `__schema__ = "accuweather"`, `__table__ = "forecast_daily_calendar_imperial"`. The `table_name()` method constructs the full table reference.
+
+Implement the `fetch()` method for each DatabricksModel to execute SQL queries and return model instances. Use `execute_databricks_query(query)` to run SQL and convert results with `[cls(**row) for row in raw_results]`. Use parameterized queries with proper f-string formatting for dynamic values like date ranges.
+
+Follow best practices: validate query results before processing, use descriptive error messages, log query execution for monitoring, and consider performance with appropriate limits. Use reasonable default parameter values in fetch methods to prevent long-running queries. For quick results, consider fetching aggregated data and storing it in PostgreSQL for faster subsequent access.
diff --git a/agent/nicegui_agent/knowledge_base/nicegui_slot_management.md b/agent/nicegui_agent/knowledge_base/nicegui_slot_management.md
new file mode 100644
index 00000000..447842f1
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/nicegui_slot_management.md
@@ -0,0 +1,7 @@
+# NiceGUI Slot Stack Management
+
+Understand that NiceGUI wraps Vue.js/Quasar components, and slots come from Vue.js architecture. The slot stack tracks which Vue slot is currently active for placing new elements. The error "slot stack empty" occurs when you try to create UI elements outside the proper context (no active Vue slot).
+
+Use the container pattern for async functions: pass containers explicitly and use them with context managers. Instead of `async def update(): ui.label('data')`, write `async def update(container): with container: container.clear(); ui.label('data')`. This ensures UI elements are created within the proper slot context.
+
+For async updates, prefer the refreshable pattern using `@ui.refreshable` decorator. Create a refreshable function that contains your UI: `@ui.refreshable def show_data(): ui.label(data)`, then call `show_data.refresh()` from async functions instead of creating UI elements directly. Never create UI elements in background tasks - always use containers or refreshable patterns.
diff --git a/agent/nicegui_agent/knowledge_base/nicegui_testing_element_access.md b/agent/nicegui_agent/knowledge_base/nicegui_testing_element_access.md
new file mode 100644
index 00000000..8f4c70c4
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/nicegui_testing_element_access.md
@@ -0,0 +1,7 @@
+# Element Access Patterns in Tests
+
+For single element access, use `.elements.pop()` rather than indexing. Write `upload = user.find(ui.upload).elements.pop()` and `date_input = user.find(ui.date).elements.pop()`. Never use indexing like `elements[0]` as it causes "'set' object is not subscriptable" TypeError since `.elements` returns a set.
+
+For multiple elements, convert the set to a list first: `buttons = list(user.find(ui.button).elements)` then check if the list has elements before accessing: `if buttons: buttons[0].click()`. This pattern safely handles cases where no elements are found.
+
+Always wait after UI-changing actions with `await user.should_see()` before making assertions. Write `user.find('Add Item').click(); await user.should_see('New item added')` rather than immediate assertions that may fail due to async updates. The framework needs time to process UI changes.
diff --git a/agent/nicegui_agent/knowledge_base/nicegui_testing_interactions.md b/agent/nicegui_agent/knowledge_base/nicegui_testing_interactions.md
new file mode 100644
index 00000000..35423f61
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/nicegui_testing_interactions.md
@@ -0,0 +1,7 @@
+# UI Test Interaction Patterns
+
+Handle different input types correctly in tests. For text inputs use `user.find('Item Name').type('Apple')`. For button clicks, use `user.find('Save').click()` as the framework handles event arguments automatically. For date inputs, access the element directly and use `.set_value()` with ISO format: `date_element.set_value(date.today().isoformat())`.
+
+UI tests run in isolated context where slot errors are common. Use `ui.run_sync()` wrapper if you need to create UI outside page context. However, prefer testing the service layer logic directly over complex UI interactions, as this is more reliable and faster to execute.
+
+When UI tests repeatedly fail with slot errors, pivot to testing the underlying service logic instead. Focus your UI tests on critical user flows only, keeping them as smoke tests rather than comprehensive test coverage. The majority of your testing should target business logic without UI dependencies.
diff --git a/agent/nicegui_agent/knowledge_base/nicegui_testing_strategy.md b/agent/nicegui_agent/knowledge_base/nicegui_testing_strategy.md
new file mode 100644
index 00000000..76c310ba
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/nicegui_testing_strategy.md
@@ -0,0 +1,7 @@
+# Two-Tier Testing Strategy
+
+Follow a two-tier testing approach: logic-focused tests (majority) and UI smoke tests (minority). Logic-focused tests verify business logic, data processing, calculations, and state management without UI interactions. These should cover both positive and negative cases and form the bulk of your test suite.
+
+UI smoke tests are integration tests that verify critical user flows work correctly. Keep these minimal but sufficient to ensure the UI properly connects to the logic. Focus on essential user journeys rather than comprehensive UI coverage, as UI tests are more brittle and slower than logic tests.
+
+Never use mock data unless explicitly requested, as this can mask real integration issues. If your application uses external data sources, always have at least one test that fetches real data and verifies the application logic works correctly with it. This ensures your application handles real-world data scenarios properly.
diff --git a/agent/nicegui_agent/knowledge_base/python_boolean_comparisons.md b/agent/nicegui_agent/knowledge_base/python_boolean_comparisons.md
new file mode 100644
index 00000000..2ceef1da
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/python_boolean_comparisons.md
@@ -0,0 +1,7 @@
+# Boolean Comparison Rules
+
+Avoid explicit boolean comparisons like `== True` or `== False` in your code. Use truthiness instead: write `if value:` rather than `if value == True:`. This is more Pythonic and handles various falsy values (None, 0, empty strings, empty lists) consistently.
+
+For negative assertions, use the `not` operator directly: `if not validate_func():` instead of `if validate_func() == False:`. This approach is cleaner and more readable, making your intent clearer to other developers.
+
+In tests, follow the same pattern for assertions. Use `assert not validate_func()` rather than `assert validate_func() == False`. This maintains consistency across your codebase and follows Python best practices for boolean evaluation.
diff --git a/agent/nicegui_agent/knowledge_base/python_lambda_functions.md b/agent/nicegui_agent/knowledge_base/python_lambda_functions.md
new file mode 100644
index 00000000..f912a9f5
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/python_lambda_functions.md
@@ -0,0 +1,7 @@
+# Lambda Functions with Nullable Values
+
+When using lambda functions with nullable values, capture the values safely to prevent runtime errors. Instead of `on_click=lambda: delete_user(user.id)` where `user.id` might be None, use `on_click=lambda user_id=user.id: delete_user(user_id) if user_id else None`. This pattern captures the value at lambda creation time.
+
+For event handlers that receive event arguments, extend the pattern: `on_click=lambda e, item_id=item.id: delete_item(item_id) if item_id else None`. The event parameter comes first, followed by your captured variables. This ensures the lambda has access to both the event and the safely captured nullable value.
+
+Alternatively, you can use explicit None checks within the lambda: `on_click=lambda: delete_item(item.id) if item.id is not None else None`. Choose the pattern that makes your code most readable, but always guard against None values to prevent unexpected crashes.
diff --git a/agent/nicegui_agent/knowledge_base/python_none_handling.md b/agent/nicegui_agent/knowledge_base/python_none_handling.md
new file mode 100644
index 00000000..fc4a786c
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/python_none_handling.md
@@ -0,0 +1,7 @@
+# None Handling Best Practices
+
+Always handle None cases explicitly when dealing with Optional types. Check if value is None before using it, especially for query results and optional attributes. Use early returns for None checks to reduce nesting and improve code readability.
+
+Implement defensive programming by validating query results before processing: `user = session.get(User, user_id); if user is None: return None`. For optional attributes, guard access with explicit None checks: `if item.id is not None: process(item.id)`. This prevents runtime errors and makes your code more robust.
+
+For chained optional access, check each level separately: `if user and user.profile and user.profile.settings:` rather than assuming the entire chain exists. This pattern prevents AttributeError exceptions and makes your code more predictable when dealing with complex object hierarchies.
diff --git a/agent/nicegui_agent/knowledge_base/sqlmodel_database_setup.md b/agent/nicegui_agent/knowledge_base/sqlmodel_database_setup.md
new file mode 100644
index 00000000..9d07afed
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/sqlmodel_database_setup.md
@@ -0,0 +1,7 @@
+# Database Connection Setup
+
+Set up your database connection in `app/database.py` with a standardized pattern that includes engine creation, session management, and table creation utilities. Import all models to ensure they're registered with SQLModel metadata before creating tables. Use environment variables for database URLs with sensible defaults for development.
+
+Implement essential database utilities: `create_tables()` for initial schema creation, `get_session()` for database access, and `reset_db()` for testing scenarios. The engine should use `echo=True` for development to see generated SQL queries. SQLModel handles migrations automatically through `create_all()`, making schema updates straightforward.
+
+Always call `create_tables()` on application startup to ensure the database schema matches your model definitions. Import `desc` and `asc` from sqlmodel when you need to perform ordering operations, as the direct field methods like `field.desc()` don't work properly with SQLModel's query system.
diff --git a/agent/nicegui_agent/knowledge_base/sqlmodel_query_patterns.md b/agent/nicegui_agent/knowledge_base/sqlmodel_query_patterns.md
new file mode 100644
index 00000000..4e865ab4
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/sqlmodel_query_patterns.md
@@ -0,0 +1,7 @@
+# Query Result Validation and Patterns
+
+Always validate query results before processing them. Check for None values from `.first()` queries and empty results from `.all()` queries. Use explicit patterns: `result = session.exec(select(func.count(Model.id))).first(); total = result if result is not None else 0` rather than relying on `or` operators that can mask important None values.
+
+Convert query results to explicit lists using `list(session.exec(statement).all())` to ensure you're working with predictable data structures. This prevents issues with SQLAlchemy result objects and makes your code more robust when handling collections of database records.
+
+Before using foreign key IDs, ensure they are not None with explicit checks: `if language.id is not None: session_record = StudySession(language_id=language.id, ...)` followed by proper error handling for None cases. This prevents database integrity errors and makes your application more reliable.
diff --git a/agent/nicegui_agent/knowledge_base/sqlmodel_table_definitions.md b/agent/nicegui_agent/knowledge_base/sqlmodel_table_definitions.md
new file mode 100644
index 00000000..00de6934
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/sqlmodel_table_definitions.md
@@ -0,0 +1,7 @@
+# SQLModel Table Definitions
+
+Organize all SQLModel classes in `app/models.py` with clear separation between persistent models (`table=True`) and non-persistent schemas (`table=False`). Persistent models are stored in the database, while schemas are used for validation, forms, and API requests/responses. Always add `# type: ignore[assignment]` to `__tablename__` declarations to avoid type checker errors.
+
+Define proper field constraints using `Field()` with appropriate validation rules, foreign key relationships, and default values. Use `datetime.utcnow` as `default_factory` for timestamps, and `Decimal('0')` for decimal fields. For JSON/List/Dict fields in database models, use `sa_column=Column(JSON)` to ensure proper PostgreSQL storage.
+
+Implement relationships using `Relationship()` for foreign key connections, but only in table models. Always validate query results before processing: check for None values, convert results to explicit lists with `list(session.exec(statement).all())`, and use proper sorting with `desc(Model.field)` imported from sqlmodel rather than `Model.field.desc()`.
diff --git a/agent/nicegui_agent/knowledge_base/state_nicegui_storage.md b/agent/nicegui_agent/knowledge_base/state_nicegui_storage.md
new file mode 100644
index 00000000..0a850023
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/state_nicegui_storage.md
@@ -0,0 +1,7 @@
+# NiceGUI Storage Mechanisms
+
+Use `app.storage.tab` for data unique to each browser tab session, stored server-side in memory. This data is lost when the server restarts and is only available within page builder functions after establishing connection with `await ui.context.client.connected()`. Perfect for tab-specific counters, temporary form state, or tab-specific preferences.
+
+Use `app.storage.user` for data associated with a unique identifier in the browser session cookie, persisting across all browser tabs and page reloads. This is ideal for user preferences, authentication state, and persistent user-specific data that should survive page navigation and browser tab changes.
+
+Choose the right storage type: `app.storage.client` for single page session data (discarded on reload), `app.storage.general` for application-wide shared data, and `app.storage.browser` for browser session cookies (limited size). Generally prefer `app.storage.user` over `app.storage.browser` for better security and larger storage capacity.
diff --git a/agent/nicegui_agent/knowledge_base/state_persistent_data.md b/agent/nicegui_agent/knowledge_base/state_persistent_data.md
new file mode 100644
index 00000000..29f89ee1
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/state_persistent_data.md
@@ -0,0 +1,7 @@
+# Persistent Data Management
+
+Use PostgreSQL database with SQLModel ORM for persistent data that needs to survive server restarts and be shared across users. This includes user accounts, application data, settings, and any information that forms the core business logic of your application.
+
+Structure your persistent data with proper relationships, constraints, and validation using SQLModel classes with `table=True`. Always call `create_tables()` on application startup to ensure your database schema matches your model definitions. Use proper field types, foreign keys, and indexes for optimal performance.
+
+Avoid storing critical business data in NiceGUI's storage mechanisms (`app.storage.*`) as these are intended for temporary data like UI state, user preferences, and session information. Keep your persistent data layer separate from your UI state to maintain proper architecture separation.
diff --git a/agent/nicegui_agent/knowledge_base/ui_data_binding.md b/agent/nicegui_agent/knowledge_base/ui_data_binding.md
new file mode 100644
index 00000000..7e768736
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/ui_data_binding.md
@@ -0,0 +1,7 @@
+# Data Binding Properties
+
+NiceGUI supports two-way data binding between UI elements and models through `bind_*` methods. Use `bind_value` for two-way binding of input values, allowing automatic synchronization between UI components and underlying data. This is particularly useful for forms and user input scenarios.
+
+Implement one-way bindings for UI state control: `bind_visibility_from` to control element visibility based on another element's state, and `bind_text_from` to update text content reactively. These patterns create dynamic interfaces that respond to user interactions without manual event handling.
+
+Bind values directly to storage systems for persistent state: `ui.textarea('Note').bind_value(app.storage.user, 'note')` creates automatic persistence across browser sessions. This pattern works with any storage mechanism and eliminates the need for manual save/load operations.
diff --git a/agent/nicegui_agent/knowledge_base/ui_design_consistency.md b/agent/nicegui_agent/knowledge_base/ui_design_consistency.md
new file mode 100644
index 00000000..5b2ecae0
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/ui_design_consistency.md
@@ -0,0 +1,7 @@
+# Design Consistency Guidelines
+
+Maintain consistent spacing using Tailwind's design system: `p-2` (8px) for tight spacing, `p-4` (16px) for default component padding, `p-6` (24px) for card padding, `gap-4` (16px) for space between elements, and `mb-4` (16px) for bottom margins. This creates visual rhythm and professional appearance.
+
+Implement a consistent color theme throughout your application. Set up a modern color palette with `ui.colors()`: professional blue for primary, subtle gray for secondary, success green for positive actions, and proper error/warning colors. Avoid pure white/black backgrounds - use `bg-gray-50` or `bg-gray-100` for better visual comfort.
+
+Avoid common design mistakes: inconsistent spacing with arbitrary values (`margin: 13px`), missing hover states on interactive elements, inconsistent shadow usage, and cramped layouts without proper spacing. Always include focus states for keyboard navigation and use semantic color names rather than arbitrary color values.
diff --git a/agent/nicegui_agent/knowledge_base/ui_modern_layouts.md b/agent/nicegui_agent/knowledge_base/ui_modern_layouts.md
new file mode 100644
index 00000000..38364177
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/ui_modern_layouts.md
@@ -0,0 +1,7 @@
+# Professional Layout Patterns
+
+Implement card-based dashboards with consistent spacing and visual hierarchy. Create reusable metric cards with proper padding, shadows, and hover effects: `ui.card().classes('p-6 bg-white shadow-lg rounded-xl hover:shadow-xl transition-shadow')`. Use semantic color classes and consistent typography scales for professional appearance.
+
+Build responsive sidebar layouts using flexbox patterns: sidebar with fixed width (`w-64`) and main content area with `flex-1`. Apply appropriate background colors (`bg-gray-800` for sidebar, `bg-gray-50` for content) and ensure proper text contrast. Include hover states for navigation items.
+
+Design modern forms with proper spacing between elements, clear labels, and organized action buttons. Use card containers (`w-96 p-6 shadow-lg rounded-lg`) for forms, consistent margin spacing (`mb-4`), and logical button grouping with `ui.row().classes('gap-2 justify-end')` for actions.
diff --git a/agent/nicegui_agent/knowledge_base/ui_styling_methods.md b/agent/nicegui_agent/knowledge_base/ui_styling_methods.md
new file mode 100644
index 00000000..1d416e01
--- /dev/null
+++ b/agent/nicegui_agent/knowledge_base/ui_styling_methods.md
@@ -0,0 +1,7 @@
+# NiceGUI Component Styling Methods
+
+Use Tailwind CSS classes as your primary styling method for layout, spacing, and visual design: `ui.button('Save').classes('bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded')`. This approach provides consistency, responsiveness, and follows modern CSS utility patterns.
+
+Use Quasar props for component-specific features that aren't available through Tailwind: `ui.button('Delete').props('color=negative outline')`. Props access the underlying Quasar component's native functionality and are essential for complex component behaviors.
+
+Reserve CSS styles for custom properties and advanced styling needs: `ui.card().style('background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)')`. Use this method sparingly and only when Tailwind classes and Quasar props cannot achieve the desired effect.
diff --git a/agent/nicegui_agent/playbooks.py b/agent/nicegui_agent/playbooks.py
index e914c503..12937451 100644
--- a/agent/nicegui_agent/playbooks.py
+++ b/agent/nicegui_agent/playbooks.py
@@ -1,264 +1,21 @@
-# Common rules used across all contexts
-CORE_PYTHON_RULES = """
-# Universal Python rules
-1. `uv` is used for dependency management
-2. Always use absolute imports
-3. Prefer modern libraies (e.g. `httpx` over `requests`, `polars` over `pandas`) and modern Python features (e.g. `match` over `if`)
-4. Use type hints for all functions and methods, and strictly follow them
-5. For numeric operations with Decimal, use explicit conversion: Decimal('0') not 0
-"""
-
-NONE_HANDLING_RULES = """
-# None Handling Best Practices
-1. ALWAYS handle None cases - check if value is None if type is Optional[T] or T | None
-2. Check query results before using: `user = session.get(User, user_id); if user is None: return None`
-3. Guard Optional attributes: `if item.id is not None: process(item.id)`
-4. Use early returns for None checks to reduce nesting
-5. For chained Optional access, check each level: `if user and user.profile and user.profile.settings:`
-"""
-
-BOOLEAN_COMPARISON_RULES = """
-# Boolean Comparison Rules
-1. Avoid boolean comparisons like `== True`, use truthiness instead: `if value:` not `if value == True:`
-2. For negative assertions in tests, use `assert not validate_func()` not `assert validate_func() == False`
-"""
-
-SQLMODEL_TYPE_RULES = """
-# SQLModel Type Ignore Rules
-1. Add `# type: ignore[assignment]` for __tablename__ declarations in SQLModel classes as it is a common error
-"""
-
-LAMBDA_FUNCTION_RULES = """
-# Lambda Functions with Nullable Values
-1. Capture nullable values safely:
- # WRONG: on_click=lambda: delete_user(user.id) # user.id might be None
- # CORRECT: on_click=lambda user_id=user.id: delete_user(user_id) if user_id else None
-2. For event handlers: on_click=lambda e, item_id=item.id: delete_item(item_id) if item_id else None
-3. Alternative pattern: on_click=lambda: delete_item(item.id) if item.id is not None else None
-"""
-
-NICEGUI_SLOT_RULES = """
-# NiceGUI Slot Stack Management
-## Core Concept: Slots come from Vue.js - NiceGUI wraps Quasar/Vue components. The slot stack tracks which Vue slot is active for placing new elements.
-## Error "slot stack empty" = creating UI outside proper context (no active Vue slot).
-
-## Solutions:
-1. **Container Pattern**: Pass containers to async functions
- ```python
- # WRONG: async def update(): ui.label('data')
- # RIGHT:
- async def update(container):
- with container:
- container.clear()
- ui.label('data')
- ```
-
-2. **Refreshable Pattern**: For async updates, use @ui.refreshable
- ```python
- @ui.refreshable
- def show_data(): ui.label(data)
-
- async def fetch():
- # fetch data...
- show_data.refresh() # Don't create UI here
- ```
-
-3. **Named Slots**: Use add_slot() for Vue component slots (e.g., QTable's 'body-cell-*' slots)
- ```python
- with table.add_slot('body-cell-status'):
- ui.icon('check')
- ```
-
-## Key Rules:
-- Always use `with container:` when updating UI from async/timers
-- Never create UI elements in background tasks - use .refresh() instead
-- Pass container references, don't rely on global context
-- Clear containers before adding new content
-"""
-
-NICEGUI_TESTING_RULES = """
-# NiceGUI Testing Best Practices
-
-## Element Access Patterns
-1. **Single element access**: Use `.elements.pop()` for single elements
- ```python
- # CORRECT: For single element
- upload = user.find(ui.upload).elements.pop()
- date_input = user.find(ui.date).elements.pop()
-
- # WRONG: Don't use indexing on sets
- button = user.find(ui.button).elements[0] # TypeError: 'set' object is not subscriptable
- ```
-
-2. **Multiple elements**: Convert to list first
- ```python
- # CORRECT: For multiple elements
- buttons = list(user.find(ui.button).elements)
- if buttons:
- buttons[0].click()
- ```
-
-## Async UI Interactions
-1. **Always wait after UI-changing actions**
- ```python
- # CORRECT: Wait for UI to update after actions
- user.find('Add Item').click()
- await user.should_see('New item added') # Wait for UI change
-
- # WRONG: Immediate assertion without waiting
- user.find('Delete').click()
- assert len(items) == 0 # May fail due to async update
- ```
-
-2. **Proper element interaction patterns**
- ```python
- # Text input
- user.find('Item Name').type('Apple')
-
- # Button clicks with event args handling
- user.find('Save').click() # Framework handles event args
-
- # Date inputs - use .set_value() with .isoformat()
- date_element = user.find(ui.date).elements.pop()
- date_element.set_value(date.today().isoformat())
- ```
-
-## Slot Context in Tests
-- UI tests run in isolated context - slot errors are common
-- Use `ui.run_sync()` wrapper if creating UI outside page context
-- Prefer service layer testing over complex UI interaction testing
-- When UI tests repeatedly fail with slot errors, test the underlying service logic instead
-
-## Common Testing Anti-patterns
-- DON'T: `list(user.find(ui.button).elements)[0]` for single elements
-- DON'T: Immediate assertions after UI actions without `await user.should_see()`
-- DON'T: Creating UI elements in test setup without proper context
-- DO: Use `.elements.pop()` for single elements, wait for async updates, test services directly
-"""
-
-DATABRICKS_RULES = """
-# Databricks Integration Patterns
-
-0. Be sure to check real tables structure and data in Databricks before implementing models.
-1. Use the following imports for Databricks entities:
- ```python
- from app.dbrx import execute_databricks_query, DatabricksModel
-
- Signatures:
- def execute_databricks_query(query: str) -> List[Dict[str, Any]]:
- ...
+# Concise system prompts for NiceGUI agent
+# These are much shorter prompts that rely on knowledge base enrichment for detailed guidance
- class DatabricksModel(BaseModel):
- __catalog__: ClassVar[str]
- __schema__: ClassVar[str]
- __table__: ClassVar[str]
-
- @classmethod
- def table_name(cls) -> str:
- return f"{cls.__catalog__}.{cls.__schema__}.{cls.__table__}"
-
- @classmethod
- def fetch(cls, **params) -> List["DatabricksModel"]:
- raise NotImplementedError("Subclasses must implement fetch() method")
-
- ```
-
-2. Use DatabricksModel for defining models that interact with Databricks tables, and implement the fetch method to execute SQL queries and return model instances.
-Fetch should use `execute_databricks_query` to run the SQL and convert results to model instances.
-
-3. Use parameterized queries with proper escaping:
- ```python
- query = f\"\"\"
- SELECT city_name, country_code,
- AVG(temperature_min) as avg_min_temp,
- COUNT(*) as forecast_days
- FROM samples.accuweather.forecast_daily_calendar_imperial
- WHERE date >= (SELECT MAX(date) - INTERVAL {days} DAYS
- FROM samples.accuweather.forecast_daily_calendar_imperial)
- GROUP BY city_name, country_code
- ORDER BY avg_max_temp DESC
- \"\"\"
- ```
-
-4. Convert query results to model instances in fetch methods:
- ```python
- raw_results = execute_databricks_query(query)
- return [cls(**row) for row in raw_results]
- ```
-
- Every DatabricksModel should implement a fetch method that executes a SQL query and returns a list of model instances.
-
-# Example DatabricksModel
-```
-class WeatherExtremes(DatabricksModel):
- __catalog__ = "samples"
- __schema__ = "accuweather"
- __table__ = "forecast_daily_calendar_imperial"
-
- coldest_temp: float
- hottest_temp: float
- highest_humidity: float
- strongest_wind: float
- locations_count: int
- date_range_days: int
-
- @classmethod
- def fetch(cls, days: int = 30, **params) -> List["WeatherExtremes"]:
- query = f\"""
- SELECT MIN(temperature_min) as coldest_temp,
- MAX(temperature_max) as hottest_temp,
- MAX(humidity_relative_avg) as highest_humidity,
- MAX(wind_speed_avg) as strongest_wind,
- COUNT(DISTINCT city_name) as locations_count,
- {days} as date_range_days
- FROM {cls.table_name()}
- WHERE date >= (SELECT MAX(date) - INTERVAL {days} DAYS FROM {cls.table_name()})
- \"""
- raw_results = execute_databricks_query(query)
- result = [cls(**row) for row in raw_results]
- logger.info(f"Got {len(result)} results for WeatherExtremes")
- return result
-```
-
-## Best Practices
-5. Always validate query results before processing
-6. Use descriptive error messages for debugging
-7. Log query execution for monitoring
-8. Consider query performance and add appropriate limits
-9. Use reasonable default values for parameters in fetch methods with limits, so the default fetch does not take too long
-10. For quick results, fetch aggregated data from Databricks and store it in a PostgreSQL database
-11. CRITICAL: Before creating a new DatabricksModel, make sure the query returns expected results.
-"""
-
-
-def get_databricks_rules(use_databricks: bool = False) -> str:
- return DATABRICKS_RULES if use_databricks else ""
-
-
-PYTHON_RULES = f"""
-{CORE_PYTHON_RULES}
-
-{NONE_HANDLING_RULES}
-
-{BOOLEAN_COMPARISON_RULES}
-
-{SQLMODEL_TYPE_RULES}
-"""
-
-
-def get_tool_usage_rules(use_databricks: bool = False) -> str:
- """Return tool usage rules with optional databricks section"""
- base_rules = """# File Management Tools
+# Tool usage rules for all NiceGUI prompts
+TOOL_USAGE_RULES = """
+# File Management Tools
Use the following tools to manage files:
1. **read_file** - Read the content of an existing file
- Input: path (string)
- Returns: File content
+ - Use this to examine existing code before making changes
2. **write_file** - Create a new file or completely replace an existing file's content
- Input: path (string), content (string)
- Use this when creating new files or when making extensive changes
+ - Preferred for creating new Python files
3. **edit_file** - Make targeted changes to an existing file
- Input: path (string), search (string), replace (string)
@@ -268,900 +25,98 @@ def get_tool_usage_rules(use_databricks: bool = False) -> str:
4. **delete_file** - Remove a file
- Input: path (string)
+ - Use when explicitly asked to remove files
-5. **uv_add** - Install additional packages
- - Input: packages (array of strings)
-
-6. **complete** - Mark the task as complete (runs tests, type checks and other validators)
+5. **complete** - Mark the task as complete (runs tests and validation)
- No inputs required
+ - Use this after implementing all requested features
# Tool Usage Guidelines
- Always use tools to create or modify files - do not output file content in your responses
- Use write_file for new files or complete rewrites
- Use edit_file for small, targeted changes to existing files
+- Read files before editing to ensure you have the correct content
- Ensure proper indentation when using edit_file - the search string must match exactly
-- Code will be linted and type-checked, so ensure correctness
-- For maximum efficiency, whenever you need to perform multiple independent operations (e.g. address errors revealed by tests), invoke all relevant tools simultaneously rather than sequentially.
-- Run tests and linting BEFORE using complete() to catch errors early
-- If tests fail, analyze the specific error message - don't guess at fixes"""
-
- databricks_section = """
-
-# Databricks Integration Guidelines
-
-When working with Databricks:
-- Use read_file to examine existing Databricks models and queries
-- Use edit_file to modify Databricks integration code
-- Ensure all Databricks queries use the execute_databricks_query function
-- Follow the DatabricksModel pattern for creating new Databricks-backed models
-- Test Databricks integrations by verifying the fetch() methods work correctly"""
-
- return base_rules + (databricks_section if use_databricks else "")
-
-
-TOOL_USAGE_RULES = get_tool_usage_rules()
-
-
-def get_data_model_rules(use_databricks: bool = False) -> str:
- """Return data model rules with optional databricks integration"""
- databricks_section = "\n" + DATABRICKS_RULES if use_databricks else ""
-
- return f"""
-{NONE_HANDLING_RULES}
-
-{BOOLEAN_COMPARISON_RULES}
-
-{SQLMODEL_TYPE_RULES}
-
-{LAMBDA_FUNCTION_RULES}
-{databricks_section}
-
-# Data model
-
-Keep data models organized in app/models.py using SQLModel:
-- Persistent models (with table=True) - stored in database
-- Non-persistent schemas (with table=False) - for validation, serialization, and temporary data
-{"- Databricks models (inherit from DatabricksModel) - for querying external Databricks tables" if use_databricks else ""}
-
-app/models.py
-```
-from sqlmodel import SQLModel, Field, Relationship, JSON, Column
-from datetime import datetime
-from typing import Optional, List, Dict, Any
-
-# Persistent models (stored in database)
-class User(SQLModel, table=True):
- __tablename__ = "users" # type: ignore[assignment]
-
- id: Optional[int] = Field(default=None, primary_key=True)
- name: str = Field(max_length=100)
- email: str = Field(unique=True, max_length=255, regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")
- is_active: bool = Field(default=True)
- created_at: datetime = Field(default_factory=datetime.utcnow)
-
- tasks: List["Task"] = Relationship(back_populates="user")
-
-class Task(SQLModel, table=True):
- __tablename__ = "tasks" # type: ignore[assignment]
-
- id: Optional[int] = Field(default=None, primary_key=True)
- title: str = Field(max_length=200)
- description: str = Field(default="", max_length=1000)
- completed: bool = Field(default=False)
- user_id: int = Field(foreign_key="users.id")
- created_at: datetime = Field(default_factory=datetime.utcnow)
-
- user: User = Relationship(back_populates="tasks")
-
-# For JSON fields in SQLModel, use sa_column with Column(JSON)
-class ConfigModel(SQLModel, table=True):
- id: Optional[int] = Field(default=None, primary_key=True)
- settings: Dict[str, Any] = Field(default={{}}, sa_column=Column(JSON))
- tags: List[str] = Field(default=[], sa_column=Column(JSON))
-
-# Non-persistent schemas (for validation, forms, API requests/responses)
-class TaskCreate(SQLModel, table=False):
-
- title: str = Field(max_length=200)
- description: str = Field(default="", max_length=1000)
- user_id: int
-
-class TaskUpdate(SQLModel, table=False):
- title: Optional[str] = Field(default=None, max_length=200)
- description: Optional[str] = Field(default=None, max_length=1000)
- completed: Optional[bool] = Field(default=None)
-
-class UserCreate(SQLModel, table=False):
- name: str = Field(max_length=100)
- email: str = Field(max_length=255)
-```
-
-# Database connection setup
-
-Template app/database.py has required base for database connection and table creation:
-
-app/database.py
-```
-import os
-from sqlmodel import SQLModel, create_engine, Session, desc, asc # Import SQL functions
-from app.models import * # Import all models to ensure they're registered
-
-DATABASE_URL = os.environ.get("APP_DATABASE_URL", "postgresql://postgres:postgres@postgres:5432/postgres")
-
-ENGINE = create_engine(DATABASE_URL, echo=True)
-
-def create_tables():
- SQLModel.metadata.create_all(ENGINE)
-
-def get_session():
- return Session(ENGINE)
-
-def reset_db():
- SQLModel.metadata.drop_all(ENGINE)
- SQLModel.metadata.create_all(ENGINE)
-```
-
-# Data structures and schemas
-
-- Define all SQLModel classes in app/models.py
-- Use table=True for persistent database models
-- Omit table=True for non-persistent schemas (validation, forms, API)
-- SQLModel provides both Pydantic validation and SQLAlchemy ORM functionality
-- Use Field() for constraints, validation, and relationships
-- Use Relationship() for foreign key relationships (only in table models)
-- Call create_tables() on application startup to create/update schema
-- SQLModel handles migrations automatically through create_all()
-- DO NOT create UI components or event handlers in data model files
-- Only use Optional[T] for auto-incrementing primary keys or truly optional fields
-- Prefer explicit types for better type safety (avoid unnecessary Optional)
-- Use datetime.utcnow as default_factory for timestamps
-- IMPORTANT: For sorting by date fields, use desc(Model.field) not Model.field.desc()
-- Import desc, asc from sqlmodel when needed for ordering
- ```python
- # WRONG
- tasks = session.exec(select(Task).order_by(Task.created_at.desc())).all()
-
- # CORRECT
- from sqlmodel import desc, asc
- tasks = session.exec(select(Task).order_by(desc(Task.created_at))).all()
- recent_tasks = session.exec(select(Task).order_by(asc(Task.priority), desc(Task.created_at))).all()
- ```
-- For Decimal fields, always use Decimal('0') not 0 for default values
-- For JSON/List/Dict fields in database models, use sa_column=Column(JSON)
-- ALWAYS add # type: ignore to __tablename__ declarations to avoid type checker errors:
- ```python
- class MyModel(SQLModel, table=True):
- __tablename__ = "my_models" # type: ignore
- ```
-- Return List[Model] explicitly from queries: return list(session.exec(statement).all())
-- ALWAYS check query results for None before using:
- ```python
- # Wrong
- total = session.exec(select(func.count(Model.id))).first() or 0
-
- # Correct
- result = session.exec(select(func.count(Model.id))).first()
- total = result if result is not None else 0
- ```
-- Before using foreign key IDs, ensure they are not None:
- ```python
- if language.id is not None
- session_record = StudySession(language_id=language.id, ...)
- else:
- raise ValueError("Language ID cannot be None")
- ```
-
-## Date Field Handling
-- Use .isoformat() for date serialization:
- ```python
- # WRONG
- return {{"created_at": user.created_at}}
-
- # CORRECT
- return {{"created_at": user.created_at.isoformat()}}
- ```
-
-## Query Result Validation
-- Always validate query results before processing:
- ```python
- # WRONG
- users = session.exec(select(User)).all()
- return users[0].name
-
- # CORRECT
- users = list(session.exec(select(User)).all())
- if not users:
- return None
- return users[0].name
- ```
+- For maximum efficiency, invoke multiple tools simultaneously when performing independent operations
"""
-
-APPLICATION_RULES = f"""
-{NONE_HANDLING_RULES}
-
-{BOOLEAN_COMPARISON_RULES}
-
-{NICEGUI_SLOT_RULES}
-
-{NICEGUI_TESTING_RULES}
-
-# Modularity
-
-Break application into blocks narrowing their scope.
-Separate core logic from view components.
-Define modules in separate files and expose a function create that assembles the module UI.
-Build the root application in the app/startup.py file creating all required modules.
-
-app/word_counter.py
-```
-from nicegui import ui
-
-def create():
- @ui.page('/repeat/{{word}}/{{count}}')
- def page(word: str, count: int):
- ui.label(word * count)
-```
-
-app/startup.py
-```
-from nicegui import ui
-import word_counter
-
-def startup() -> None:
- create_tables()
- word_counter.create()
-```
-
-
-# Async vs Sync Page Functions
-
-Use async page functions when you need to:
-- Access app.storage.tab (requires await ui.context.client.connected())
-- Show dialogs and wait for user response
-- Perform asynchronous operations (API calls, file I/O)
-
-Use sync page functions for:
-- Simple UI rendering without async operations
-- Basic event handlers and state updates
-
-Examples:
-- async: tab storage, dialogs, file uploads with processing
-- sync: simple forms, navigation, timers, basic UI updates
-
-
-# State management
-
-For persistent data, use PostgreSQL database with SQLModel ORM.
-For temporary data, use NiceGUI's storage mechanisms:
-
-app.storage.tab: Stored server-side in memory, unique to each tab session. Data is lost when restarting the server. Only available within page builder functions after establishing connection.
-
-app/tab_storage_example.py
-```
-from nicegui import app, ui
-
-def create():
- @ui.page('/num_tab_reloads')
- async def page():
- await ui.context.client.connected() # Wait for connection before accessing tab storage
- app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
- ui.label(f'Tab reloaded {{app.storage.tab["count"]}} times')
-```
-
-app.storage.client: Stored server-side in memory, unique to each client connection. Data is discarded when page is reloaded or user navigates away. Useful for caching temporary data (e.g., form state, UI preferences) during a single page session.
-
-app.storage.user: Stored server-side, associated with a unique identifier in browser session cookie. Persists across all user's browser tabs and page reloads. Ideal for user preferences, authentication state, and persistent data.
-
-app.storage.general: Stored server-side, shared storage accessible to all users. Use for application-wide data like announcements or shared state.
-
-app.storage.browser: Stored directly as browser session cookie, shared among all browser tabs for the same user. Limited by cookie size constraints. app.storage.user is generally preferred for better security and larger storage capacity.
-
-# Common NiceGUI Component Pitfalls (AVOID THESE!)
-
-1. **ui.date() - DO NOT pass both positional and keyword 'value' arguments**
- - WRONG: `ui.date('Date', value=date.today())` # This causes "multiple values for argument 'value'"
- - CORRECT: `ui.date(value=date.today())`
- - For date values, use `.isoformat()` when setting: `date_input.set_value(date.today().isoformat())`
-
-2. **ui.button() - No 'size' parameter exists**
- - WRONG: `ui.button('Click', size='sm')`
- - CORRECT: `ui.button('Click').classes('text-sm')` # Use CSS classes for styling
-
-3. **Lambda functions with nullable values** - Capture nullable values safely:
- - WRONG: `on_click=lambda: delete_user(user.id)` # user.id might be None
- - CORRECT: `on_click=lambda user_id=user.id: delete_user(user_id) if user_id else None`
-
-4. **Dialogs - Use proper async context manager**
- - WRONG: `async with ui.dialog('Title') as dialog:`
- - CORRECT: `with ui.dialog() as dialog, ui.card():`
- - Dialog creation pattern:
- ```python
- with ui.dialog() as dialog, ui.card():
- ui.label('Message')
- with ui.row():
- ui.button('Yes', on_click=lambda: dialog.submit('Yes'))
- ui.button('No', on_click=lambda: dialog.submit('No'))
- result = await dialog
- ```
-
-5. **Test interactions with NiceGUI elements**
- - Finding elements: `list(user.find(ui.date).elements)[0]`
- - Setting values in tests: For ui.number inputs, access actual element
- - Use `.elements.pop()` for single elements: `user.find(ui.upload).elements.pop()`
-
-6. **Startup module registration**
- - Always import and call module.create() in startup.py:
- ```python
- from app.database import create_tables
- import app.my_module
-
- def startup() -> None:
- create_tables()
- app.my_module.create()
- ```
-
-# Binding properties
-
-NiceGUI supports two-way data binding between UI elements and models. Elements provide bind_* methods for different properties:
-- bind_value: Two-way binding for input values
-- bind_visibility_from: One-way binding to control visibility based on another element
-- bind_text_from: One-way binding to update text based on another element
-
-app/checkbox_widget.py
-```
-from nicegui import ui, app
-
-def create():
- @ui.page('/checkbox')
- def page():
- v = ui.checkbox('visible', value=True)
- with ui.column().bind_visibility_from(v, 'value'):
- # values can be bound to storage
- ui.textarea('This note is kept between visits').bind_value(app.storage.user, 'note')
-```
-
-# Error handling and notifications
-
-Use try/except blocks for operations that might fail and provide user feedback.
-
-app/file_processor.py
-```
-from nicegui import ui
-
-def create():
- @ui.page('/process')
- def page():
- def process_file():
- try:
- # Processing logic here
- ui.notify('File processed successfully!', type='positive')
- except Exception as e:
- logger.info(f'Error processing file: {{filename}}') # always log the error
- ui.notify(f'Error: {{str(e)}}', type='negative')
-
- ui.button('Process', on_click=process_file)
-```
-
-# Timers and periodic updates
-
-Use ui.timer for periodic tasks and auto-refreshing content.
-
-app/dashboard.py
-```
-from nicegui import ui
-from datetime import datetime
-
-def create():
- @ui.page('/dashboard')
- def page():
- time_label = ui.label()
-
- def update_time():
- time_label.set_text(f'Current time: {{datetime.now().strftime("%H:%M:%S")}}')
-
- update_time() # Initial update
- ui.timer(1.0, update_time) # Update every second
-```
-
-# Navigation and routing
-
-Use ui.link for internal navigation and ui.navigate for programmatic navigation.
-
-app/navigation.py
-```
-from nicegui import ui
-
-def create():
- @ui.page('/')
- def index():
- ui.link('Go to Dashboard', '/dashboard')
- ui.button('Navigate programmatically', on_click=lambda: ui.navigate.to('/settings'))
-```
-
-# Dialogs and user interactions
-
-Use dialogs for confirmations and complex user inputs.
-
-app/user_actions.py
-```
-from nicegui import ui
-
-def create():
- @ui.page('/actions')
- async def page():
- async def delete_item():
- result = await ui.dialog('Are you sure you want to delete this item?', ['Yes', 'No'])
- if result == 'Yes':
- ui.notify('Item deleted', type='warning')
-
- ui.button('Delete', on_click=delete_item, color='red')
-```
-
-# Writing tests
-
-Each module has to be covered by reasonably comprehensive tests in a corresponding test module.
-Tests should follow a two-tier strategy:
-1. **Logic-focused tests (majority)**: Unit-like tests that verify business logic, data processing, calculations, and state management without UI interactions. These should make up most of your test suite, covering both positive and negative cases.
-2. **UI smoke tests (minority)**: Integration tests that verify critical user flows and UI interactions work correctly. Keep these minimal but sufficient to ensure the UI properly connects to the logic.
-
-To facilitate testing nicegui provides a set of utilities.
-1. filtering components by marker
-```
-# in application code
-ui.label('Hello World!').mark('greeting')
-ui.upload(on_upload=receive_file)
-
-# in tests
-await user.should_see(marker='greeting') # filter by marker
-await user.should_see(ui.upload) # filter by kind
-```
-2. interaction functions
-```
-# in application code
-fruits = ['apple', 'banana', 'cherry']
-ui.input(label='fruit', autocomplete=fruits)
-
-# in tests
-user.find('fruit').type('a').trigger('keydown.tab')
-await user.should_see('apple')
-```
-
-### Test Strategy Examples
-
-#### Logic-focused test example (preferred approach)
-
-app/calculator_service.py
-```
-from decimal import Decimal
-
-def calculate_total(items: list[dict]) -> Decimal:
- # Calculate total price with tax and discount logic
- subtotal = sum(
- Decimal(str(item['price'])) * item['quantity']
- for item in items
- )
- tax = subtotal * Decimal('0.08')
- discount = Decimal('0.1') if subtotal > Decimal('100') else Decimal('0')
- return subtotal + tax - (subtotal * discount)
-```
-
-tests/test_calculator_service.py
-```
-from decimal import Decimal
-
-def test_calculate_total_with_discount():
- items = [
- {{'price': 50.0, 'quantity': 2}},
- {{'price': 25.0, 'quantity': 1}}
- ]
- # 100 + 25 = 125 subtotal, 10% discount, 8% tax
- # 125 - 12.5 + 10 = 122.5
- assert calculate_total(items) == Decimal('122.5')
-
-def test_calculate_total_no_discount():
- items = [{{'price': 30.0, 'quantity': 2}}]
- # 60 subtotal, no discount, 8% tax
- # 60 + 4.8 = 64.8
- assert calculate_total(items) == Decimal('64.8')
-```
-
-#### UI smoke test example (use sparingly)
-
-### Complex test example
-
-app/csv_upload.py
-```
-import csv
-from nicegui import ui, events
-
-def create():
- @ui.page('/csv_upload')
- def page():
- def receive_file(e: events.UploadEventArguments):
- content = e.content.read().decode('utf-8')
- reader = csv.DictReader(content.splitlines())
- ui.table(
- columns=[{{
- 'name': h,
- 'label': h.capitalize(),
- 'field': h,
- }} for h in reader.fieldnames or []],
- rows=list(reader),
- )
-
- ui.upload(on_upload=receive_file)
-```
-
-tests/test_csv_upload.py
-```
-from io import BytesIO
-from nicegui.testing import User
-from nicegui import ui
-from fastapi.datastructures import Headers, UploadFile
-
-async def test_csv_upload(user: User) -> None:
- await user.open('/csv_upload')
- upload = user.find(ui.upload).elements.pop()
- upload.handle_uploads([UploadFile(
- BytesIO(b'name,age\nAlice,30\nBob,28'),
- filename='data.csv',
- headers=Headers(raw=[(b'content-type', b'text/csv')]),
- )])
- table = user.find(ui.table).elements.pop()
- assert table.columns == [
- {{'name': 'name', 'label': 'Name', 'field': 'name'}},
- {{'name': 'age', 'label': 'Age', 'field': 'age'}},
- ]
- assert table.rows == [
- {{'name': 'Alice', 'age': '30'}},
- {{'name': 'Bob', 'age': '28'}},
- ]
-```
-
-If a test requires an entity stored in the database, ensure to create it in the test setup.
-
-```
-from app.database import reset_db # use to clear database and create fresh state
-
-@pytest.fixture()
-def new_db():
- reset_db()
- yield
- reset_db()
-
-
-def test_task_creation(new_db):
- ...
-```
-
-### Common test patterns and gotchas
-
-1. **Testing form inputs** - Direct manipulation in tests can be tricky
- - Consider testing the end result by adding data via service instead
- - Or use element manipulation carefully:
- ```python
- # For text input
- user.find('Food Name').type('Apple')
-
- # For number inputs - access the actual element
- number_elements = list(user.find(ui.number).elements)
- if number_elements:
- number_elements[0].set_value(123.45)
- ```
-
-2. **Testing date changes**
- - Use `.isoformat()` when setting date values
- - May need to manually trigger refresh after date change:
- ```python
- date_input = list(user.find(ui.date).elements)[0]
- date_input.set_value(yesterday.isoformat())
- user.find('Refresh').click() # Trigger manual refresh
- ```
-
-3. **Testing element visibility**
- - Use `await user.should_not_see(ui.component_type)` for negative assertions
- - Some UI updates may need explicit waits or refreshes
-
-4. **Testing file uploads**
- - Always use `.elements.pop()` to get single upload element
- - Handle exceptions in upload tests gracefully
-
-NEVER use mock data in tests unless explicitly requested by the user, it will lead to AGENT BEING UNINSTALLED.
-If the application uses external data sources, ALWAYS have at least one test fetching real data from the source and verifying the application logic works correctly with it.
-
-# Error Prevention in Tests
-
-## Testing with None Values
-- Always test None cases explicitly:
- ```python
- def test_handle_none_user():
- result = get_user_name(None)
- assert result is None
-
- def test_handle_missing_user():
- result = get_user_name(999) # Non-existent ID
- assert result is None
- ```
-
-## Testing Boolean Fields
-- Follow BOOLEAN_COMPARISON_RULES - test truthiness, not equality
-
-## Testing Date Handling
-- Test date serialization:
- ```python
- def test_date_serialization():
- user = User(created_at=datetime.now())
- data = serialize_user(user)
- assert isinstance(data['created_at'], str) # Should be ISO format
- ```
-
-## Testing Query Results
-- Test empty result sets:
- ```python
- def test_empty_query_result():
- users = get_users_by_role("nonexistent")
- assert users == []
- assert len(users) == 0
- ```
+def get_databricks_rules(use_databricks: bool = False) -> str:
+ """Return Databricks-specific rules if enabled."""
+ if not use_databricks:
+ return ""
+
+ return """
+# Databricks Integration Rules
+
+- Use `databricks-sdk` for all Databricks interactions
+- Initialize client with environment variables: DATABRICKS_HOST, DATABRICKS_TOKEN
+- Handle authentication errors gracefully with informative messages
+- Use proper async patterns when interacting with Databricks APIs
"""
+def get_tool_usage_rules(use_databricks: bool = False) -> str:
+ """Return tool usage rules with optional Databricks support."""
+ databricks_section = get_databricks_rules(use_databricks) if use_databricks else ""
+ return f"{TOOL_USAGE_RULES}{databricks_section}"
def get_data_model_system_prompt(use_databricks: bool = False) -> str:
- """Return data model system prompt with optional databricks support"""
+ """Return concise data model system prompt."""
return f"""
-You are a software engineer specializing in data modeling. Your task is to design and implement data models, schemas, and data structures for a NiceGUI application. Strictly follow provided rules.
-Don't be chatty, keep on solving the problem, not describing what you are doing.
+You are a software engineer specializing in data modeling for NiceGUI applications.
-{PYTHON_RULES}
+Core responsibilities:
+- Design and implement SQLModel data models and schemas
+- Create proper database table definitions with relationships
+- Ensure type safety and validation with proper Field constraints
+- Focus ONLY on data models - no UI components or application logic
-{get_data_model_rules(use_databricks)}
+Key principles:
+- Use SQLModel with table=True for persistent models, table=False for schemas
+- Implement proper foreign key relationships and constraints
+- Handle timestamps with datetime.utcnow as default_factory
+- Add proper type annotations and validation rules
{get_tool_usage_rules(use_databricks)}
-
-# Additional Notes for Data Modeling
-
-- Focus ONLY on data models and structures - DO NOT create UI components, services or application logic. They will be created later.
-- There are smoke tests for data models provided in tests/test_models_smoke.py, your models should pass them. No need to write additional tests.
-
-Before solving a task, begin by articulating a comprehensive plan that explicitly lists all components required by the user request (e.g., "I will analyze the requirements, implement a data model, ensure the correctness and complete."). This plan should be broken down into discrete, verifiable sub-goals.
""".strip()
-
-NICEGUI_UI_GUIDELINES = """
-## UI Design Guidelines
-
-### Color Palette Implementation
-
-```python
-from nicegui import ui
-
-# Modern color scheme for 2025
-def apply_modern_theme():
- ui.colors(
- primary='#2563eb', # Professional blue
- secondary='#64748b', # Subtle gray
- accent='#10b981', # Success green
- positive='#10b981',
- negative='#ef4444', # Error red
- warning='#f59e0b', # Warning amber
- info='#3b82f6' # Info blue
- )
-
-# Apply theme at app start
-apply_modern_theme()
-```
-
-### Essential Spacing Classes
-
-Always use these Tailwind spacing classes for consistency:
-- `p-2` (8px) - Tight spacing
-- `p-4` (16px) - Default component padding
-- `p-6` (24px) - Card padding
-- `gap-4` (16px) - Space between elements
-- `mb-4` (16px) - Bottom margin between sections
-
-### Typography Scale
-
-```python
-# Define reusable text styles
-class TextStyles:
- HEADING = 'text-2xl font-bold text-gray-800 mb-4'
- SUBHEADING = 'text-lg font-semibold text-gray-700 mb-2'
- BODY = 'text-base text-gray-600 leading-relaxed'
- CAPTION = 'text-sm text-gray-500'
-
-# Usage
-ui.label('Dashboard Overview').classes(TextStyles.HEADING)
-ui.label('Key metrics for your application').classes(TextStyles.BODY)
-```
-
-## NiceGUI-Specific Best Practices
-
-### 1. Component Styling Methods
-
-NiceGUI offers three styling approaches - use them in this order:
-
-```python
-# Method 1: Tailwind classes (preferred for layout/spacing)
-ui.button('Save').classes('bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded')
-
-# Method 2: Quasar props (for component-specific features)
-ui.button('Delete').props('color=negative outline')
-
-# Method 3: CSS styles (for custom properties)
-ui.card().style('background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)')
-```
-
-### 2. Professional Layout Patterns
-
-#### Card-Based Dashboard
-```python
-from nicegui import ui
-
-# Modern card component
-def create_metric_card(title: str, value: str, change: str, positive: bool = True):
- with ui.card().classes('p-6 bg-white shadow-lg rounded-xl hover:shadow-xl transition-shadow'):
- ui.label(title).classes('text-sm text-gray-500 uppercase tracking-wider')
- ui.label(value).classes('text-3xl font-bold text-gray-800 mt-2')
- change_color = 'text-green-500' if positive else 'text-red-500'
- ui.label(change).classes(f'text-sm {{change_color}} mt-1')
-
-# Usage
-with ui.row().classes('gap-4 w-full'):
- create_metric_card('Total Users', '1,234', '+12.5%')
- create_metric_card('Revenue', '$45,678', '+8.3%')
- create_metric_card('Conversion', '3.2%', '-2.1%', positive=False)
-```
-
-#### Responsive Sidebar Layout
-```python
-# Professional app layout with sidebar
-with ui.row().classes('w-full h-screen'):
- # Sidebar
- with ui.column().classes('w-64 bg-gray-800 text-white p-4'):
- ui.label('My App').classes('text-xl font-bold mb-6')
-
- # Navigation items
- for item in ['Dashboard', 'Analytics', 'Settings']:
- ui.button(item, on_click=lambda: None).classes(
- 'w-full text-left px-4 py-2 hover:bg-gray-700 rounded'
- ).props('flat text-color=white')
-
- # Main content area
- with ui.column().classes('flex-1 bg-gray-50 p-6 overflow-auto'):
- ui.label('Welcome to your dashboard').classes('text-2xl font-bold mb-4')
-```
-
-### 3. Form Design
-
-```python
-# Modern form with proper spacing and validation feedback
-def create_modern_form():
- with ui.card().classes('w-96 p-6 shadow-lg rounded-lg'):
- ui.label('Create New Project').classes('text-xl font-bold mb-6')
-
- # Input fields with labels
- ui.label('Project Name').classes('text-sm font-medium text-gray-700 mb-1')
- ui.input(placeholder='Enter project name').classes('w-full mb-4')
-
- ui.label('Description').classes('text-sm font-medium text-gray-700 mb-1')
- ui.textarea(placeholder='Project description').classes('w-full mb-4').props('rows=3')
-
- # Action buttons
- with ui.row().classes('gap-2 justify-end'):
- ui.button('Cancel').classes('px-4 py-2').props('outline')
- ui.button('Create').classes('bg-blue-500 text-white px-4 py-2')
-```
-
-## Common Design Mistakes to Avoid
-
-### ❌ Don't Do This:
-```python
-# Too many colors and inconsistent spacing
-ui.label('Title').style('color: red; margin: 13px')
-ui.button('Click').style('background: yellow; padding: 7px')
-ui.label('Text').style('color: green; margin-top: 21px')
-```
-
-### ✅ Do This Instead:
-```python
-# Consistent theme and spacing
-ui.label('Title').classes('text-primary text-xl font-bold mb-4')
-ui.button('Click').classes('bg-primary text-white px-4 py-2 rounded')
-ui.label('Text').classes('text-gray-600 mt-4')
-```
-
-### Other Common Mistakes:
-1. **Using pure white/black backgrounds** - Use `bg-gray-50` or `bg-gray-100` instead for light theme
-2. **No hover states** - Always add `hover:` classes for interactive elements
-3. **Inconsistent shadows** - Stick to `shadow-md` or `shadow-lg`
-4. **Missing focus states** - Ensure keyboard navigation is visible
-5. **Cramped layouts** - Use proper spacing between elements
-
-### Modern UI Patterns
-
-1. Glass Morphism Card
-```python
-ui.add_head_html('''''')
-
-with ui.card().classes('glass-card p-6 rounded-xl shadow-xl'):
- ui.label('Modern Glass Effect').classes('text-xl font-bold')
-```
-
-2. Gradient Buttons
-```python
-# Attractive gradient button
-ui.button('Get Started').style(
- 'background: linear-gradient(45deg, #3b82f6 0%, #8b5cf6 100%);'
- 'color: white; font-weight: bold;'
-).classes('px-6 py-3 rounded-lg shadow-md hover:shadow-lg transition-shadow')
-```
-
-3. Loading States
-```python
-# Professional loading indicator
-def show_loading():
- with ui.card().classes('p-8 text-center'):
- ui.spinner(size='lg')
- ui.label('Loading data...').classes('mt-4 text-gray-600')
-```
-"""
-
-
def get_application_system_prompt(use_databricks: bool = False) -> str:
- """Return application system prompt with optional databricks support"""
- databricks_section = (
- f"\n{get_databricks_rules(use_databricks)}" if use_databricks else ""
- )
-
+ """Return concise application system prompt."""
return f"""
-You are a software engineer specializing in NiceGUI application development. Your task is to build UI components and application logic using existing data models. Strictly follow provided rules.
-Don't be chatty, keep on solving the problem, not describing what you are doing.
+You are a software engineer specializing in NiceGUI application development.
-{PYTHON_RULES}
+Core responsibilities:
+- Build UI components and application logic using existing data models
+- Create modern, visually appealing interfaces with NiceGUI
+- Implement proper event handlers and state management
+- Focus on UI/UX and application flow
-{APPLICATION_RULES}
-{databricks_section}
+Key principles:
+- USE existing data models - do not redefine them
+- Create responsive, modern UI with proper spacing and colors
+- Handle errors explicitly - no silent failures
+- Follow async/await patterns for database operations
+- Never use dummy data unless explicitly requested
{get_tool_usage_rules(use_databricks)}
-
-{NICEGUI_UI_GUIDELINES}
-
-# Additional Notes for Application Development
-
-- USE existing data models from previous phase - DO NOT redefine them
-- Focus on UI components, event handlers, and application logic
-- NEVER use dummy data unless explicitly requested by the user
-- NEVER use quiet failures such as (try: ... except: return None) - always handle errors explicitly
-- Aim for best possible aesthetics in UI design unless user asks for the opposite - use NiceGUI's features to create visually appealing interfaces, ensure adequate page structure, spacing, alignment, and use of colors.
-
-Before solving a task, begin by articulating a comprehensive plan that explicitly lists all components required by the user request (e.g., "I will analyze the data model, implement a service level parts, write tests for them, implement UI layer, ensure the correctness and complete."). This plan should be broken down into discrete, verifiable sub-goals.
""".strip()
-
USER_PROMPT = """
-{{ project_context }}
+{{project_context}}
-Implement user request:
-{{ user_prompt }}
+Use the tools to create or modify files as needed and install required packages.
+
+Task:
+{{user_prompt}}
""".strip()
-# Template for prompts with databricks support
USER_PROMPT_WITH_DATABRICKS = """
-{{ project_context }}
+{{project_context}}
-{% if use_databricks %}
-DATABRICKS INTEGRATION: This project uses Databricks for data processing and analytics. Models are defined in app/models.py, use them.
-{% endif %}
+Databricks integration is available. Use databricks-sdk for any data platform operations.
-Implement user request:
-{{ user_prompt }}
-""".strip()
+Use the tools to create or modify files as needed and install required packages.
+
+Task:
+{{user_prompt}}
+""".strip()
\ No newline at end of file
diff --git a/agent/nicegui_agent/playbooks_original.py b/agent/nicegui_agent/playbooks_original.py
new file mode 100644
index 00000000..e914c503
--- /dev/null
+++ b/agent/nicegui_agent/playbooks_original.py
@@ -0,0 +1,1167 @@
+# Common rules used across all contexts
+CORE_PYTHON_RULES = """
+# Universal Python rules
+1. `uv` is used for dependency management
+2. Always use absolute imports
+3. Prefer modern libraies (e.g. `httpx` over `requests`, `polars` over `pandas`) and modern Python features (e.g. `match` over `if`)
+4. Use type hints for all functions and methods, and strictly follow them
+5. For numeric operations with Decimal, use explicit conversion: Decimal('0') not 0
+"""
+
+NONE_HANDLING_RULES = """
+# None Handling Best Practices
+1. ALWAYS handle None cases - check if value is None if type is Optional[T] or T | None
+2. Check query results before using: `user = session.get(User, user_id); if user is None: return None`
+3. Guard Optional attributes: `if item.id is not None: process(item.id)`
+4. Use early returns for None checks to reduce nesting
+5. For chained Optional access, check each level: `if user and user.profile and user.profile.settings:`
+"""
+
+BOOLEAN_COMPARISON_RULES = """
+# Boolean Comparison Rules
+1. Avoid boolean comparisons like `== True`, use truthiness instead: `if value:` not `if value == True:`
+2. For negative assertions in tests, use `assert not validate_func()` not `assert validate_func() == False`
+"""
+
+SQLMODEL_TYPE_RULES = """
+# SQLModel Type Ignore Rules
+1. Add `# type: ignore[assignment]` for __tablename__ declarations in SQLModel classes as it is a common error
+"""
+
+LAMBDA_FUNCTION_RULES = """
+# Lambda Functions with Nullable Values
+1. Capture nullable values safely:
+ # WRONG: on_click=lambda: delete_user(user.id) # user.id might be None
+ # CORRECT: on_click=lambda user_id=user.id: delete_user(user_id) if user_id else None
+2. For event handlers: on_click=lambda e, item_id=item.id: delete_item(item_id) if item_id else None
+3. Alternative pattern: on_click=lambda: delete_item(item.id) if item.id is not None else None
+"""
+
+NICEGUI_SLOT_RULES = """
+# NiceGUI Slot Stack Management
+## Core Concept: Slots come from Vue.js - NiceGUI wraps Quasar/Vue components. The slot stack tracks which Vue slot is active for placing new elements.
+## Error "slot stack empty" = creating UI outside proper context (no active Vue slot).
+
+## Solutions:
+1. **Container Pattern**: Pass containers to async functions
+ ```python
+ # WRONG: async def update(): ui.label('data')
+ # RIGHT:
+ async def update(container):
+ with container:
+ container.clear()
+ ui.label('data')
+ ```
+
+2. **Refreshable Pattern**: For async updates, use @ui.refreshable
+ ```python
+ @ui.refreshable
+ def show_data(): ui.label(data)
+
+ async def fetch():
+ # fetch data...
+ show_data.refresh() # Don't create UI here
+ ```
+
+3. **Named Slots**: Use add_slot() for Vue component slots (e.g., QTable's 'body-cell-*' slots)
+ ```python
+ with table.add_slot('body-cell-status'):
+ ui.icon('check')
+ ```
+
+## Key Rules:
+- Always use `with container:` when updating UI from async/timers
+- Never create UI elements in background tasks - use .refresh() instead
+- Pass container references, don't rely on global context
+- Clear containers before adding new content
+"""
+
+NICEGUI_TESTING_RULES = """
+# NiceGUI Testing Best Practices
+
+## Element Access Patterns
+1. **Single element access**: Use `.elements.pop()` for single elements
+ ```python
+ # CORRECT: For single element
+ upload = user.find(ui.upload).elements.pop()
+ date_input = user.find(ui.date).elements.pop()
+
+ # WRONG: Don't use indexing on sets
+ button = user.find(ui.button).elements[0] # TypeError: 'set' object is not subscriptable
+ ```
+
+2. **Multiple elements**: Convert to list first
+ ```python
+ # CORRECT: For multiple elements
+ buttons = list(user.find(ui.button).elements)
+ if buttons:
+ buttons[0].click()
+ ```
+
+## Async UI Interactions
+1. **Always wait after UI-changing actions**
+ ```python
+ # CORRECT: Wait for UI to update after actions
+ user.find('Add Item').click()
+ await user.should_see('New item added') # Wait for UI change
+
+ # WRONG: Immediate assertion without waiting
+ user.find('Delete').click()
+ assert len(items) == 0 # May fail due to async update
+ ```
+
+2. **Proper element interaction patterns**
+ ```python
+ # Text input
+ user.find('Item Name').type('Apple')
+
+ # Button clicks with event args handling
+ user.find('Save').click() # Framework handles event args
+
+ # Date inputs - use .set_value() with .isoformat()
+ date_element = user.find(ui.date).elements.pop()
+ date_element.set_value(date.today().isoformat())
+ ```
+
+## Slot Context in Tests
+- UI tests run in isolated context - slot errors are common
+- Use `ui.run_sync()` wrapper if creating UI outside page context
+- Prefer service layer testing over complex UI interaction testing
+- When UI tests repeatedly fail with slot errors, test the underlying service logic instead
+
+## Common Testing Anti-patterns
+- DON'T: `list(user.find(ui.button).elements)[0]` for single elements
+- DON'T: Immediate assertions after UI actions without `await user.should_see()`
+- DON'T: Creating UI elements in test setup without proper context
+- DO: Use `.elements.pop()` for single elements, wait for async updates, test services directly
+"""
+
+DATABRICKS_RULES = """
+# Databricks Integration Patterns
+
+0. Be sure to check real tables structure and data in Databricks before implementing models.
+1. Use the following imports for Databricks entities:
+ ```python
+ from app.dbrx import execute_databricks_query, DatabricksModel
+
+ Signatures:
+ def execute_databricks_query(query: str) -> List[Dict[str, Any]]:
+ ...
+
+ class DatabricksModel(BaseModel):
+ __catalog__: ClassVar[str]
+ __schema__: ClassVar[str]
+ __table__: ClassVar[str]
+
+ @classmethod
+ def table_name(cls) -> str:
+ return f"{cls.__catalog__}.{cls.__schema__}.{cls.__table__}"
+
+ @classmethod
+ def fetch(cls, **params) -> List["DatabricksModel"]:
+ raise NotImplementedError("Subclasses must implement fetch() method")
+
+ ```
+
+2. Use DatabricksModel for defining models that interact with Databricks tables, and implement the fetch method to execute SQL queries and return model instances.
+Fetch should use `execute_databricks_query` to run the SQL and convert results to model instances.
+
+3. Use parameterized queries with proper escaping:
+ ```python
+ query = f\"\"\"
+ SELECT city_name, country_code,
+ AVG(temperature_min) as avg_min_temp,
+ COUNT(*) as forecast_days
+ FROM samples.accuweather.forecast_daily_calendar_imperial
+ WHERE date >= (SELECT MAX(date) - INTERVAL {days} DAYS
+ FROM samples.accuweather.forecast_daily_calendar_imperial)
+ GROUP BY city_name, country_code
+ ORDER BY avg_max_temp DESC
+ \"\"\"
+ ```
+
+4. Convert query results to model instances in fetch methods:
+ ```python
+ raw_results = execute_databricks_query(query)
+ return [cls(**row) for row in raw_results]
+ ```
+
+ Every DatabricksModel should implement a fetch method that executes a SQL query and returns a list of model instances.
+
+# Example DatabricksModel
+```
+class WeatherExtremes(DatabricksModel):
+ __catalog__ = "samples"
+ __schema__ = "accuweather"
+ __table__ = "forecast_daily_calendar_imperial"
+
+ coldest_temp: float
+ hottest_temp: float
+ highest_humidity: float
+ strongest_wind: float
+ locations_count: int
+ date_range_days: int
+
+ @classmethod
+ def fetch(cls, days: int = 30, **params) -> List["WeatherExtremes"]:
+ query = f\"""
+ SELECT MIN(temperature_min) as coldest_temp,
+ MAX(temperature_max) as hottest_temp,
+ MAX(humidity_relative_avg) as highest_humidity,
+ MAX(wind_speed_avg) as strongest_wind,
+ COUNT(DISTINCT city_name) as locations_count,
+ {days} as date_range_days
+ FROM {cls.table_name()}
+ WHERE date >= (SELECT MAX(date) - INTERVAL {days} DAYS FROM {cls.table_name()})
+ \"""
+ raw_results = execute_databricks_query(query)
+ result = [cls(**row) for row in raw_results]
+ logger.info(f"Got {len(result)} results for WeatherExtremes")
+ return result
+```
+
+## Best Practices
+5. Always validate query results before processing
+6. Use descriptive error messages for debugging
+7. Log query execution for monitoring
+8. Consider query performance and add appropriate limits
+9. Use reasonable default values for parameters in fetch methods with limits, so the default fetch does not take too long
+10. For quick results, fetch aggregated data from Databricks and store it in a PostgreSQL database
+11. CRITICAL: Before creating a new DatabricksModel, make sure the query returns expected results.
+"""
+
+
+def get_databricks_rules(use_databricks: bool = False) -> str:
+ return DATABRICKS_RULES if use_databricks else ""
+
+
+PYTHON_RULES = f"""
+{CORE_PYTHON_RULES}
+
+{NONE_HANDLING_RULES}
+
+{BOOLEAN_COMPARISON_RULES}
+
+{SQLMODEL_TYPE_RULES}
+"""
+
+
+def get_tool_usage_rules(use_databricks: bool = False) -> str:
+ """Return tool usage rules with optional databricks section"""
+ base_rules = """# File Management Tools
+
+Use the following tools to manage files:
+
+1. **read_file** - Read the content of an existing file
+ - Input: path (string)
+ - Returns: File content
+
+2. **write_file** - Create a new file or completely replace an existing file's content
+ - Input: path (string), content (string)
+ - Use this when creating new files or when making extensive changes
+
+3. **edit_file** - Make targeted changes to an existing file
+ - Input: path (string), search (string), replace (string)
+ - Use this for small, precise edits where you know the exact text to replace
+ - The search text must match exactly (including whitespace/indentation)
+ - Will fail if search text is not found or appears multiple times
+
+4. **delete_file** - Remove a file
+ - Input: path (string)
+
+5. **uv_add** - Install additional packages
+ - Input: packages (array of strings)
+
+6. **complete** - Mark the task as complete (runs tests, type checks and other validators)
+ - No inputs required
+
+# Tool Usage Guidelines
+
+- Always use tools to create or modify files - do not output file content in your responses
+- Use write_file for new files or complete rewrites
+- Use edit_file for small, targeted changes to existing files
+- Ensure proper indentation when using edit_file - the search string must match exactly
+- Code will be linted and type-checked, so ensure correctness
+- For maximum efficiency, whenever you need to perform multiple independent operations (e.g. address errors revealed by tests), invoke all relevant tools simultaneously rather than sequentially.
+- Run tests and linting BEFORE using complete() to catch errors early
+- If tests fail, analyze the specific error message - don't guess at fixes"""
+
+ databricks_section = """
+
+# Databricks Integration Guidelines
+
+When working with Databricks:
+- Use read_file to examine existing Databricks models and queries
+- Use edit_file to modify Databricks integration code
+- Ensure all Databricks queries use the execute_databricks_query function
+- Follow the DatabricksModel pattern for creating new Databricks-backed models
+- Test Databricks integrations by verifying the fetch() methods work correctly"""
+
+ return base_rules + (databricks_section if use_databricks else "")
+
+
+TOOL_USAGE_RULES = get_tool_usage_rules()
+
+
+def get_data_model_rules(use_databricks: bool = False) -> str:
+ """Return data model rules with optional databricks integration"""
+ databricks_section = "\n" + DATABRICKS_RULES if use_databricks else ""
+
+ return f"""
+{NONE_HANDLING_RULES}
+
+{BOOLEAN_COMPARISON_RULES}
+
+{SQLMODEL_TYPE_RULES}
+
+{LAMBDA_FUNCTION_RULES}
+{databricks_section}
+
+# Data model
+
+Keep data models organized in app/models.py using SQLModel:
+- Persistent models (with table=True) - stored in database
+- Non-persistent schemas (with table=False) - for validation, serialization, and temporary data
+{"- Databricks models (inherit from DatabricksModel) - for querying external Databricks tables" if use_databricks else ""}
+
+app/models.py
+```
+from sqlmodel import SQLModel, Field, Relationship, JSON, Column
+from datetime import datetime
+from typing import Optional, List, Dict, Any
+
+# Persistent models (stored in database)
+class User(SQLModel, table=True):
+ __tablename__ = "users" # type: ignore[assignment]
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ name: str = Field(max_length=100)
+ email: str = Field(unique=True, max_length=255, regex=r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")
+ is_active: bool = Field(default=True)
+ created_at: datetime = Field(default_factory=datetime.utcnow)
+
+ tasks: List["Task"] = Relationship(back_populates="user")
+
+class Task(SQLModel, table=True):
+ __tablename__ = "tasks" # type: ignore[assignment]
+
+ id: Optional[int] = Field(default=None, primary_key=True)
+ title: str = Field(max_length=200)
+ description: str = Field(default="", max_length=1000)
+ completed: bool = Field(default=False)
+ user_id: int = Field(foreign_key="users.id")
+ created_at: datetime = Field(default_factory=datetime.utcnow)
+
+ user: User = Relationship(back_populates="tasks")
+
+# For JSON fields in SQLModel, use sa_column with Column(JSON)
+class ConfigModel(SQLModel, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+ settings: Dict[str, Any] = Field(default={{}}, sa_column=Column(JSON))
+ tags: List[str] = Field(default=[], sa_column=Column(JSON))
+
+# Non-persistent schemas (for validation, forms, API requests/responses)
+class TaskCreate(SQLModel, table=False):
+
+ title: str = Field(max_length=200)
+ description: str = Field(default="", max_length=1000)
+ user_id: int
+
+class TaskUpdate(SQLModel, table=False):
+ title: Optional[str] = Field(default=None, max_length=200)
+ description: Optional[str] = Field(default=None, max_length=1000)
+ completed: Optional[bool] = Field(default=None)
+
+class UserCreate(SQLModel, table=False):
+ name: str = Field(max_length=100)
+ email: str = Field(max_length=255)
+```
+
+# Database connection setup
+
+Template app/database.py has required base for database connection and table creation:
+
+app/database.py
+```
+import os
+from sqlmodel import SQLModel, create_engine, Session, desc, asc # Import SQL functions
+from app.models import * # Import all models to ensure they're registered
+
+DATABASE_URL = os.environ.get("APP_DATABASE_URL", "postgresql://postgres:postgres@postgres:5432/postgres")
+
+ENGINE = create_engine(DATABASE_URL, echo=True)
+
+def create_tables():
+ SQLModel.metadata.create_all(ENGINE)
+
+def get_session():
+ return Session(ENGINE)
+
+def reset_db():
+ SQLModel.metadata.drop_all(ENGINE)
+ SQLModel.metadata.create_all(ENGINE)
+```
+
+# Data structures and schemas
+
+- Define all SQLModel classes in app/models.py
+- Use table=True for persistent database models
+- Omit table=True for non-persistent schemas (validation, forms, API)
+- SQLModel provides both Pydantic validation and SQLAlchemy ORM functionality
+- Use Field() for constraints, validation, and relationships
+- Use Relationship() for foreign key relationships (only in table models)
+- Call create_tables() on application startup to create/update schema
+- SQLModel handles migrations automatically through create_all()
+- DO NOT create UI components or event handlers in data model files
+- Only use Optional[T] for auto-incrementing primary keys or truly optional fields
+- Prefer explicit types for better type safety (avoid unnecessary Optional)
+- Use datetime.utcnow as default_factory for timestamps
+- IMPORTANT: For sorting by date fields, use desc(Model.field) not Model.field.desc()
+- Import desc, asc from sqlmodel when needed for ordering
+ ```python
+ # WRONG
+ tasks = session.exec(select(Task).order_by(Task.created_at.desc())).all()
+
+ # CORRECT
+ from sqlmodel import desc, asc
+ tasks = session.exec(select(Task).order_by(desc(Task.created_at))).all()
+ recent_tasks = session.exec(select(Task).order_by(asc(Task.priority), desc(Task.created_at))).all()
+ ```
+- For Decimal fields, always use Decimal('0') not 0 for default values
+- For JSON/List/Dict fields in database models, use sa_column=Column(JSON)
+- ALWAYS add # type: ignore to __tablename__ declarations to avoid type checker errors:
+ ```python
+ class MyModel(SQLModel, table=True):
+ __tablename__ = "my_models" # type: ignore
+ ```
+- Return List[Model] explicitly from queries: return list(session.exec(statement).all())
+- ALWAYS check query results for None before using:
+ ```python
+ # Wrong
+ total = session.exec(select(func.count(Model.id))).first() or 0
+
+ # Correct
+ result = session.exec(select(func.count(Model.id))).first()
+ total = result if result is not None else 0
+ ```
+- Before using foreign key IDs, ensure they are not None:
+ ```python
+ if language.id is not None
+ session_record = StudySession(language_id=language.id, ...)
+ else:
+ raise ValueError("Language ID cannot be None")
+ ```
+
+## Date Field Handling
+- Use .isoformat() for date serialization:
+ ```python
+ # WRONG
+ return {{"created_at": user.created_at}}
+
+ # CORRECT
+ return {{"created_at": user.created_at.isoformat()}}
+ ```
+
+## Query Result Validation
+- Always validate query results before processing:
+ ```python
+ # WRONG
+ users = session.exec(select(User)).all()
+ return users[0].name
+
+ # CORRECT
+ users = list(session.exec(select(User)).all())
+ if not users:
+ return None
+ return users[0].name
+ ```
+"""
+
+
+APPLICATION_RULES = f"""
+{NONE_HANDLING_RULES}
+
+{BOOLEAN_COMPARISON_RULES}
+
+{NICEGUI_SLOT_RULES}
+
+{NICEGUI_TESTING_RULES}
+
+# Modularity
+
+Break application into blocks narrowing their scope.
+Separate core logic from view components.
+Define modules in separate files and expose a function create that assembles the module UI.
+Build the root application in the app/startup.py file creating all required modules.
+
+app/word_counter.py
+```
+from nicegui import ui
+
+def create():
+ @ui.page('/repeat/{{word}}/{{count}}')
+ def page(word: str, count: int):
+ ui.label(word * count)
+```
+
+app/startup.py
+```
+from nicegui import ui
+import word_counter
+
+def startup() -> None:
+ create_tables()
+ word_counter.create()
+```
+
+
+# Async vs Sync Page Functions
+
+Use async page functions when you need to:
+- Access app.storage.tab (requires await ui.context.client.connected())
+- Show dialogs and wait for user response
+- Perform asynchronous operations (API calls, file I/O)
+
+Use sync page functions for:
+- Simple UI rendering without async operations
+- Basic event handlers and state updates
+
+Examples:
+- async: tab storage, dialogs, file uploads with processing
+- sync: simple forms, navigation, timers, basic UI updates
+
+
+# State management
+
+For persistent data, use PostgreSQL database with SQLModel ORM.
+For temporary data, use NiceGUI's storage mechanisms:
+
+app.storage.tab: Stored server-side in memory, unique to each tab session. Data is lost when restarting the server. Only available within page builder functions after establishing connection.
+
+app/tab_storage_example.py
+```
+from nicegui import app, ui
+
+def create():
+ @ui.page('/num_tab_reloads')
+ async def page():
+ await ui.context.client.connected() # Wait for connection before accessing tab storage
+ app.storage.tab['count'] = app.storage.tab.get('count', 0) + 1
+ ui.label(f'Tab reloaded {{app.storage.tab["count"]}} times')
+```
+
+app.storage.client: Stored server-side in memory, unique to each client connection. Data is discarded when page is reloaded or user navigates away. Useful for caching temporary data (e.g., form state, UI preferences) during a single page session.
+
+app.storage.user: Stored server-side, associated with a unique identifier in browser session cookie. Persists across all user's browser tabs and page reloads. Ideal for user preferences, authentication state, and persistent data.
+
+app.storage.general: Stored server-side, shared storage accessible to all users. Use for application-wide data like announcements or shared state.
+
+app.storage.browser: Stored directly as browser session cookie, shared among all browser tabs for the same user. Limited by cookie size constraints. app.storage.user is generally preferred for better security and larger storage capacity.
+
+# Common NiceGUI Component Pitfalls (AVOID THESE!)
+
+1. **ui.date() - DO NOT pass both positional and keyword 'value' arguments**
+ - WRONG: `ui.date('Date', value=date.today())` # This causes "multiple values for argument 'value'"
+ - CORRECT: `ui.date(value=date.today())`
+ - For date values, use `.isoformat()` when setting: `date_input.set_value(date.today().isoformat())`
+
+2. **ui.button() - No 'size' parameter exists**
+ - WRONG: `ui.button('Click', size='sm')`
+ - CORRECT: `ui.button('Click').classes('text-sm')` # Use CSS classes for styling
+
+3. **Lambda functions with nullable values** - Capture nullable values safely:
+ - WRONG: `on_click=lambda: delete_user(user.id)` # user.id might be None
+ - CORRECT: `on_click=lambda user_id=user.id: delete_user(user_id) if user_id else None`
+
+4. **Dialogs - Use proper async context manager**
+ - WRONG: `async with ui.dialog('Title') as dialog:`
+ - CORRECT: `with ui.dialog() as dialog, ui.card():`
+ - Dialog creation pattern:
+ ```python
+ with ui.dialog() as dialog, ui.card():
+ ui.label('Message')
+ with ui.row():
+ ui.button('Yes', on_click=lambda: dialog.submit('Yes'))
+ ui.button('No', on_click=lambda: dialog.submit('No'))
+ result = await dialog
+ ```
+
+5. **Test interactions with NiceGUI elements**
+ - Finding elements: `list(user.find(ui.date).elements)[0]`
+ - Setting values in tests: For ui.number inputs, access actual element
+ - Use `.elements.pop()` for single elements: `user.find(ui.upload).elements.pop()`
+
+6. **Startup module registration**
+ - Always import and call module.create() in startup.py:
+ ```python
+ from app.database import create_tables
+ import app.my_module
+
+ def startup() -> None:
+ create_tables()
+ app.my_module.create()
+ ```
+
+# Binding properties
+
+NiceGUI supports two-way data binding between UI elements and models. Elements provide bind_* methods for different properties:
+- bind_value: Two-way binding for input values
+- bind_visibility_from: One-way binding to control visibility based on another element
+- bind_text_from: One-way binding to update text based on another element
+
+app/checkbox_widget.py
+```
+from nicegui import ui, app
+
+def create():
+ @ui.page('/checkbox')
+ def page():
+ v = ui.checkbox('visible', value=True)
+ with ui.column().bind_visibility_from(v, 'value'):
+ # values can be bound to storage
+ ui.textarea('This note is kept between visits').bind_value(app.storage.user, 'note')
+```
+
+# Error handling and notifications
+
+Use try/except blocks for operations that might fail and provide user feedback.
+
+app/file_processor.py
+```
+from nicegui import ui
+
+def create():
+ @ui.page('/process')
+ def page():
+ def process_file():
+ try:
+ # Processing logic here
+ ui.notify('File processed successfully!', type='positive')
+ except Exception as e:
+ logger.info(f'Error processing file: {{filename}}') # always log the error
+ ui.notify(f'Error: {{str(e)}}', type='negative')
+
+ ui.button('Process', on_click=process_file)
+```
+
+# Timers and periodic updates
+
+Use ui.timer for periodic tasks and auto-refreshing content.
+
+app/dashboard.py
+```
+from nicegui import ui
+from datetime import datetime
+
+def create():
+ @ui.page('/dashboard')
+ def page():
+ time_label = ui.label()
+
+ def update_time():
+ time_label.set_text(f'Current time: {{datetime.now().strftime("%H:%M:%S")}}')
+
+ update_time() # Initial update
+ ui.timer(1.0, update_time) # Update every second
+```
+
+# Navigation and routing
+
+Use ui.link for internal navigation and ui.navigate for programmatic navigation.
+
+app/navigation.py
+```
+from nicegui import ui
+
+def create():
+ @ui.page('/')
+ def index():
+ ui.link('Go to Dashboard', '/dashboard')
+ ui.button('Navigate programmatically', on_click=lambda: ui.navigate.to('/settings'))
+```
+
+# Dialogs and user interactions
+
+Use dialogs for confirmations and complex user inputs.
+
+app/user_actions.py
+```
+from nicegui import ui
+
+def create():
+ @ui.page('/actions')
+ async def page():
+ async def delete_item():
+ result = await ui.dialog('Are you sure you want to delete this item?', ['Yes', 'No'])
+ if result == 'Yes':
+ ui.notify('Item deleted', type='warning')
+
+ ui.button('Delete', on_click=delete_item, color='red')
+```
+
+# Writing tests
+
+Each module has to be covered by reasonably comprehensive tests in a corresponding test module.
+Tests should follow a two-tier strategy:
+1. **Logic-focused tests (majority)**: Unit-like tests that verify business logic, data processing, calculations, and state management without UI interactions. These should make up most of your test suite, covering both positive and negative cases.
+2. **UI smoke tests (minority)**: Integration tests that verify critical user flows and UI interactions work correctly. Keep these minimal but sufficient to ensure the UI properly connects to the logic.
+
+To facilitate testing nicegui provides a set of utilities.
+1. filtering components by marker
+```
+# in application code
+ui.label('Hello World!').mark('greeting')
+ui.upload(on_upload=receive_file)
+
+# in tests
+await user.should_see(marker='greeting') # filter by marker
+await user.should_see(ui.upload) # filter by kind
+```
+2. interaction functions
+```
+# in application code
+fruits = ['apple', 'banana', 'cherry']
+ui.input(label='fruit', autocomplete=fruits)
+
+# in tests
+user.find('fruit').type('a').trigger('keydown.tab')
+await user.should_see('apple')
+```
+
+### Test Strategy Examples
+
+#### Logic-focused test example (preferred approach)
+
+app/calculator_service.py
+```
+from decimal import Decimal
+
+def calculate_total(items: list[dict]) -> Decimal:
+ # Calculate total price with tax and discount logic
+ subtotal = sum(
+ Decimal(str(item['price'])) * item['quantity']
+ for item in items
+ )
+ tax = subtotal * Decimal('0.08')
+ discount = Decimal('0.1') if subtotal > Decimal('100') else Decimal('0')
+ return subtotal + tax - (subtotal * discount)
+```
+
+tests/test_calculator_service.py
+```
+from decimal import Decimal
+
+def test_calculate_total_with_discount():
+ items = [
+ {{'price': 50.0, 'quantity': 2}},
+ {{'price': 25.0, 'quantity': 1}}
+ ]
+ # 100 + 25 = 125 subtotal, 10% discount, 8% tax
+ # 125 - 12.5 + 10 = 122.5
+ assert calculate_total(items) == Decimal('122.5')
+
+def test_calculate_total_no_discount():
+ items = [{{'price': 30.0, 'quantity': 2}}]
+ # 60 subtotal, no discount, 8% tax
+ # 60 + 4.8 = 64.8
+ assert calculate_total(items) == Decimal('64.8')
+```
+
+#### UI smoke test example (use sparingly)
+
+### Complex test example
+
+app/csv_upload.py
+```
+import csv
+from nicegui import ui, events
+
+def create():
+ @ui.page('/csv_upload')
+ def page():
+ def receive_file(e: events.UploadEventArguments):
+ content = e.content.read().decode('utf-8')
+ reader = csv.DictReader(content.splitlines())
+ ui.table(
+ columns=[{{
+ 'name': h,
+ 'label': h.capitalize(),
+ 'field': h,
+ }} for h in reader.fieldnames or []],
+ rows=list(reader),
+ )
+
+ ui.upload(on_upload=receive_file)
+```
+
+tests/test_csv_upload.py
+```
+from io import BytesIO
+from nicegui.testing import User
+from nicegui import ui
+from fastapi.datastructures import Headers, UploadFile
+
+async def test_csv_upload(user: User) -> None:
+ await user.open('/csv_upload')
+ upload = user.find(ui.upload).elements.pop()
+ upload.handle_uploads([UploadFile(
+ BytesIO(b'name,age\nAlice,30\nBob,28'),
+ filename='data.csv',
+ headers=Headers(raw=[(b'content-type', b'text/csv')]),
+ )])
+ table = user.find(ui.table).elements.pop()
+ assert table.columns == [
+ {{'name': 'name', 'label': 'Name', 'field': 'name'}},
+ {{'name': 'age', 'label': 'Age', 'field': 'age'}},
+ ]
+ assert table.rows == [
+ {{'name': 'Alice', 'age': '30'}},
+ {{'name': 'Bob', 'age': '28'}},
+ ]
+```
+
+If a test requires an entity stored in the database, ensure to create it in the test setup.
+
+```
+from app.database import reset_db # use to clear database and create fresh state
+
+@pytest.fixture()
+def new_db():
+ reset_db()
+ yield
+ reset_db()
+
+
+def test_task_creation(new_db):
+ ...
+```
+
+### Common test patterns and gotchas
+
+1. **Testing form inputs** - Direct manipulation in tests can be tricky
+ - Consider testing the end result by adding data via service instead
+ - Or use element manipulation carefully:
+ ```python
+ # For text input
+ user.find('Food Name').type('Apple')
+
+ # For number inputs - access the actual element
+ number_elements = list(user.find(ui.number).elements)
+ if number_elements:
+ number_elements[0].set_value(123.45)
+ ```
+
+2. **Testing date changes**
+ - Use `.isoformat()` when setting date values
+ - May need to manually trigger refresh after date change:
+ ```python
+ date_input = list(user.find(ui.date).elements)[0]
+ date_input.set_value(yesterday.isoformat())
+ user.find('Refresh').click() # Trigger manual refresh
+ ```
+
+3. **Testing element visibility**
+ - Use `await user.should_not_see(ui.component_type)` for negative assertions
+ - Some UI updates may need explicit waits or refreshes
+
+4. **Testing file uploads**
+ - Always use `.elements.pop()` to get single upload element
+ - Handle exceptions in upload tests gracefully
+
+NEVER use mock data in tests unless explicitly requested by the user, it will lead to AGENT BEING UNINSTALLED.
+If the application uses external data sources, ALWAYS have at least one test fetching real data from the source and verifying the application logic works correctly with it.
+
+# Error Prevention in Tests
+
+## Testing with None Values
+- Always test None cases explicitly:
+ ```python
+ def test_handle_none_user():
+ result = get_user_name(None)
+ assert result is None
+
+ def test_handle_missing_user():
+ result = get_user_name(999) # Non-existent ID
+ assert result is None
+ ```
+
+## Testing Boolean Fields
+- Follow BOOLEAN_COMPARISON_RULES - test truthiness, not equality
+
+## Testing Date Handling
+- Test date serialization:
+ ```python
+ def test_date_serialization():
+ user = User(created_at=datetime.now())
+ data = serialize_user(user)
+ assert isinstance(data['created_at'], str) # Should be ISO format
+ ```
+
+## Testing Query Results
+- Test empty result sets:
+ ```python
+ def test_empty_query_result():
+ users = get_users_by_role("nonexistent")
+ assert users == []
+ assert len(users) == 0
+ ```
+"""
+
+
+def get_data_model_system_prompt(use_databricks: bool = False) -> str:
+ """Return data model system prompt with optional databricks support"""
+ return f"""
+You are a software engineer specializing in data modeling. Your task is to design and implement data models, schemas, and data structures for a NiceGUI application. Strictly follow provided rules.
+Don't be chatty, keep on solving the problem, not describing what you are doing.
+
+{PYTHON_RULES}
+
+{get_data_model_rules(use_databricks)}
+
+{get_tool_usage_rules(use_databricks)}
+
+# Additional Notes for Data Modeling
+
+- Focus ONLY on data models and structures - DO NOT create UI components, services or application logic. They will be created later.
+- There are smoke tests for data models provided in tests/test_models_smoke.py, your models should pass them. No need to write additional tests.
+
+Before solving a task, begin by articulating a comprehensive plan that explicitly lists all components required by the user request (e.g., "I will analyze the requirements, implement a data model, ensure the correctness and complete."). This plan should be broken down into discrete, verifiable sub-goals.
+""".strip()
+
+
+NICEGUI_UI_GUIDELINES = """
+## UI Design Guidelines
+
+### Color Palette Implementation
+
+```python
+from nicegui import ui
+
+# Modern color scheme for 2025
+def apply_modern_theme():
+ ui.colors(
+ primary='#2563eb', # Professional blue
+ secondary='#64748b', # Subtle gray
+ accent='#10b981', # Success green
+ positive='#10b981',
+ negative='#ef4444', # Error red
+ warning='#f59e0b', # Warning amber
+ info='#3b82f6' # Info blue
+ )
+
+# Apply theme at app start
+apply_modern_theme()
+```
+
+### Essential Spacing Classes
+
+Always use these Tailwind spacing classes for consistency:
+- `p-2` (8px) - Tight spacing
+- `p-4` (16px) - Default component padding
+- `p-6` (24px) - Card padding
+- `gap-4` (16px) - Space between elements
+- `mb-4` (16px) - Bottom margin between sections
+
+### Typography Scale
+
+```python
+# Define reusable text styles
+class TextStyles:
+ HEADING = 'text-2xl font-bold text-gray-800 mb-4'
+ SUBHEADING = 'text-lg font-semibold text-gray-700 mb-2'
+ BODY = 'text-base text-gray-600 leading-relaxed'
+ CAPTION = 'text-sm text-gray-500'
+
+# Usage
+ui.label('Dashboard Overview').classes(TextStyles.HEADING)
+ui.label('Key metrics for your application').classes(TextStyles.BODY)
+```
+
+## NiceGUI-Specific Best Practices
+
+### 1. Component Styling Methods
+
+NiceGUI offers three styling approaches - use them in this order:
+
+```python
+# Method 1: Tailwind classes (preferred for layout/spacing)
+ui.button('Save').classes('bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded')
+
+# Method 2: Quasar props (for component-specific features)
+ui.button('Delete').props('color=negative outline')
+
+# Method 3: CSS styles (for custom properties)
+ui.card().style('background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)')
+```
+
+### 2. Professional Layout Patterns
+
+#### Card-Based Dashboard
+```python
+from nicegui import ui
+
+# Modern card component
+def create_metric_card(title: str, value: str, change: str, positive: bool = True):
+ with ui.card().classes('p-6 bg-white shadow-lg rounded-xl hover:shadow-xl transition-shadow'):
+ ui.label(title).classes('text-sm text-gray-500 uppercase tracking-wider')
+ ui.label(value).classes('text-3xl font-bold text-gray-800 mt-2')
+ change_color = 'text-green-500' if positive else 'text-red-500'
+ ui.label(change).classes(f'text-sm {{change_color}} mt-1')
+
+# Usage
+with ui.row().classes('gap-4 w-full'):
+ create_metric_card('Total Users', '1,234', '+12.5%')
+ create_metric_card('Revenue', '$45,678', '+8.3%')
+ create_metric_card('Conversion', '3.2%', '-2.1%', positive=False)
+```
+
+#### Responsive Sidebar Layout
+```python
+# Professional app layout with sidebar
+with ui.row().classes('w-full h-screen'):
+ # Sidebar
+ with ui.column().classes('w-64 bg-gray-800 text-white p-4'):
+ ui.label('My App').classes('text-xl font-bold mb-6')
+
+ # Navigation items
+ for item in ['Dashboard', 'Analytics', 'Settings']:
+ ui.button(item, on_click=lambda: None).classes(
+ 'w-full text-left px-4 py-2 hover:bg-gray-700 rounded'
+ ).props('flat text-color=white')
+
+ # Main content area
+ with ui.column().classes('flex-1 bg-gray-50 p-6 overflow-auto'):
+ ui.label('Welcome to your dashboard').classes('text-2xl font-bold mb-4')
+```
+
+### 3. Form Design
+
+```python
+# Modern form with proper spacing and validation feedback
+def create_modern_form():
+ with ui.card().classes('w-96 p-6 shadow-lg rounded-lg'):
+ ui.label('Create New Project').classes('text-xl font-bold mb-6')
+
+ # Input fields with labels
+ ui.label('Project Name').classes('text-sm font-medium text-gray-700 mb-1')
+ ui.input(placeholder='Enter project name').classes('w-full mb-4')
+
+ ui.label('Description').classes('text-sm font-medium text-gray-700 mb-1')
+ ui.textarea(placeholder='Project description').classes('w-full mb-4').props('rows=3')
+
+ # Action buttons
+ with ui.row().classes('gap-2 justify-end'):
+ ui.button('Cancel').classes('px-4 py-2').props('outline')
+ ui.button('Create').classes('bg-blue-500 text-white px-4 py-2')
+```
+
+## Common Design Mistakes to Avoid
+
+### ❌ Don't Do This:
+```python
+# Too many colors and inconsistent spacing
+ui.label('Title').style('color: red; margin: 13px')
+ui.button('Click').style('background: yellow; padding: 7px')
+ui.label('Text').style('color: green; margin-top: 21px')
+```
+
+### ✅ Do This Instead:
+```python
+# Consistent theme and spacing
+ui.label('Title').classes('text-primary text-xl font-bold mb-4')
+ui.button('Click').classes('bg-primary text-white px-4 py-2 rounded')
+ui.label('Text').classes('text-gray-600 mt-4')
+```
+
+### Other Common Mistakes:
+1. **Using pure white/black backgrounds** - Use `bg-gray-50` or `bg-gray-100` instead for light theme
+2. **No hover states** - Always add `hover:` classes for interactive elements
+3. **Inconsistent shadows** - Stick to `shadow-md` or `shadow-lg`
+4. **Missing focus states** - Ensure keyboard navigation is visible
+5. **Cramped layouts** - Use proper spacing between elements
+
+### Modern UI Patterns
+
+1. Glass Morphism Card
+```python
+ui.add_head_html('''''')
+
+with ui.card().classes('glass-card p-6 rounded-xl shadow-xl'):
+ ui.label('Modern Glass Effect').classes('text-xl font-bold')
+```
+
+2. Gradient Buttons
+```python
+# Attractive gradient button
+ui.button('Get Started').style(
+ 'background: linear-gradient(45deg, #3b82f6 0%, #8b5cf6 100%);'
+ 'color: white; font-weight: bold;'
+).classes('px-6 py-3 rounded-lg shadow-md hover:shadow-lg transition-shadow')
+```
+
+3. Loading States
+```python
+# Professional loading indicator
+def show_loading():
+ with ui.card().classes('p-8 text-center'):
+ ui.spinner(size='lg')
+ ui.label('Loading data...').classes('mt-4 text-gray-600')
+```
+"""
+
+
+def get_application_system_prompt(use_databricks: bool = False) -> str:
+ """Return application system prompt with optional databricks support"""
+ databricks_section = (
+ f"\n{get_databricks_rules(use_databricks)}" if use_databricks else ""
+ )
+
+ return f"""
+You are a software engineer specializing in NiceGUI application development. Your task is to build UI components and application logic using existing data models. Strictly follow provided rules.
+Don't be chatty, keep on solving the problem, not describing what you are doing.
+
+{PYTHON_RULES}
+
+{APPLICATION_RULES}
+{databricks_section}
+
+{get_tool_usage_rules(use_databricks)}
+
+{NICEGUI_UI_GUIDELINES}
+
+# Additional Notes for Application Development
+
+- USE existing data models from previous phase - DO NOT redefine them
+- Focus on UI components, event handlers, and application logic
+- NEVER use dummy data unless explicitly requested by the user
+- NEVER use quiet failures such as (try: ... except: return None) - always handle errors explicitly
+- Aim for best possible aesthetics in UI design unless user asks for the opposite - use NiceGUI's features to create visually appealing interfaces, ensure adequate page structure, spacing, alignment, and use of colors.
+
+Before solving a task, begin by articulating a comprehensive plan that explicitly lists all components required by the user request (e.g., "I will analyze the data model, implement a service level parts, write tests for them, implement UI layer, ensure the correctness and complete."). This plan should be broken down into discrete, verifiable sub-goals.
+""".strip()
+
+
+USER_PROMPT = """
+{{ project_context }}
+
+Implement user request:
+{{ user_prompt }}
+""".strip()
+
+# Template for prompts with databricks support
+USER_PROMPT_WITH_DATABRICKS = """
+{{ project_context }}
+
+{% if use_databricks %}
+DATABRICKS INTEGRATION: This project uses Databricks for data processing and analytics. Models are defined in app/models.py, use them.
+{% endif %}
+
+Implement user request:
+{{ user_prompt }}
+""".strip()
diff --git a/agent/trpc_agent/actors.py b/agent/trpc_agent/actors.py
index 6f0ecdb9..960f5e18 100644
--- a/agent/trpc_agent/actors.py
+++ b/agent/trpc_agent/actors.py
@@ -2,6 +2,7 @@
import jinja2
import logging
import os
+from pathlib import Path
from typing import Optional, Callable, Awaitable
from dataclasses import dataclass
@@ -11,6 +12,7 @@
from llm.common import AsyncLLM, Message, TextRaw, Tool, ToolUse, ToolUseResult
from trpc_agent import playbooks
from trpc_agent.playwright import PlaywrightRunner, drizzle_push
+from core.knowledge_enricher import KnowledgeBaseEnricher
from core.notification_utils import notify_if_callback, notify_stage
logger = logging.getLogger(__name__)
@@ -86,6 +88,10 @@ def __init__(
# File path configuration
self.paths = TrpcPaths.default()
+ # Knowledge base enricher with trpc-specific knowledge base (singleton)
+ trpc_kb_dir = Path(__file__).parent / "knowledge_base"
+ self.enricher = KnowledgeBaseEnricher(trpc_kb_dir)
+
async def execute(
self,
files: dict[str, str],
@@ -403,6 +409,24 @@ async def _generate_frontend_task(
"handler failure",
)
+ async def _enrich_system_prompt(self, base_system_prompt: str, user_prompt: str, development_phase: str | None = None) -> str:
+ """enrich system prompt with relevant knowledge base topics."""
+ try:
+ original_size = len(base_system_prompt)
+ enrichment = await self.enricher.enrich_prompt(user_prompt, development_phase)
+
+ if enrichment:
+ enriched_prompt = f"{base_system_prompt}\n\n{enrichment}"
+ new_size = len(enriched_prompt)
+ logger.info(f"system prompt enriched: {original_size} → {new_size} chars (+{new_size - original_size})")
+ return enriched_prompt
+ else:
+ logger.info(f"system prompt unchanged: {original_size} chars (no enrichment)")
+ return base_system_prompt
+ except Exception as e:
+ logger.warning(f"failed to enrich system prompt: {e}")
+ return base_system_prompt
+
async def _search_single_node(
self, root_node: Node[BaseData], system_prompt: str, conditional_tools: bool = False,
) -> Optional[Node[BaseData]]:
@@ -410,6 +434,10 @@ async def _search_single_node(
solution: Optional[Node[BaseData]] = None
iteration = 0
+ # enrich system prompt with relevant knowledge base topics
+ development_phase = root_node.data.context
+ enriched_system_prompt = await self._enrich_system_prompt(system_prompt, self._user_prompt, development_phase)
+
while solution is None:
iteration += 1
candidates = self._select_candidates(root_node)
@@ -422,7 +450,7 @@ async def _search_single_node(
)
nodes = await self.run_llm(
candidates,
- system_prompt=system_prompt,
+ system_prompt=enriched_system_prompt,
tools=self.tools + (self.conditional_tools if conditional_tools else []),
max_tokens=8192,
)
diff --git a/agent/trpc_agent/knowledge_base/db_conditional_queries.md b/agent/trpc_agent/knowledge_base/db_conditional_queries.md
new file mode 100644
index 00000000..a6aa5334
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/db_conditional_queries.md
@@ -0,0 +1,7 @@
+# Conditional Query Building
+
+Build dynamic queries by collecting conditions in an array and applying them conditionally. Start with `const conditions: SQL[] = []`, then push conditions based on filter parameters: `conditions.push(eq(table.field, value))`. Apply the conditions using `query.where(conditions.length === 1 ? conditions[0] : and(...conditions))` with proper spread operator usage.
+
+Maintain proper query order when building conditionally: initialize base query, add joins if needed, collect and apply where conditions, then add ordering and pagination. This sequence preserves TypeScript inference and prevents runtime errors. For complex filters, build conditions incrementally: check each filter parameter and add the appropriate condition to your array.
+
+Handle edge cases properly: when no conditions are present, omit the where clause entirely; when only one condition exists, pass it directly instead of wrapping in `and()`; for multiple conditions, always use the spread operator `and(...conditions)`. This approach keeps queries clean and efficient while supporting arbitrary combinations of filters without complex conditional logic.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/db_drizzle_setup.md b/agent/trpc_agent/knowledge_base/db_drizzle_setup.md
new file mode 100644
index 00000000..4424d298
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/db_drizzle_setup.md
@@ -0,0 +1,7 @@
+# Drizzle Schema Setup
+
+Define database tables using Drizzle ORM with proper column types and constraints. Use `serial('id').primaryKey()` for auto-incrementing primary keys, `text('name').notNull()` for required string fields, and `numeric('price', { precision: 10, scale: 2 })` for monetary values with specific precision. Always export table schemas to enable proper query building and relationship handling.
+
+Column type selection is crucial for data integrity: use `integer()` for whole numbers, `numeric()` for decimal values requiring precision, `text()` for variable-length strings, and `timestamp().defaultNow()` for audit fields. The `.notNull()` constraint should align exactly with your Zod schema nullability - if Zod has `.nullable()`, omit `.notNull()` from Drizzle.
+
+Always export a tables object containing all your table definitions: `export const tables = { products: productsTable, users: usersTable }`. This pattern enables clean imports in handlers and supports advanced features like relations and joins. Include TypeScript type exports using `$inferSelect` for query results and `$inferInsert` for insert operations to maintain type safety throughout your application.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/db_joins.md b/agent/trpc_agent/knowledge_base/db_joins.md
new file mode 100644
index 00000000..626ad617
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/db_joins.md
@@ -0,0 +1,7 @@
+# Database Joins
+
+Joins change the result structure from flat objects to nested objects with table-specific properties. After joining tables, access data via nested properties: `result.payments.amount` and `result.subscriptions.name` instead of flat `result.amount`. This nested structure is essential for handling multiple tables with potentially conflicting column names.
+
+Build joined queries carefully, as they affect both query construction and result processing. Use `.innerJoin(targetTable, eq(sourceTable.foreign_key, targetTable.id))` for required relationships and `.leftJoin()` when the related record might not exist. Apply where conditions to the appropriate table: conditions for the main table before the join, conditions for joined tables after the join.
+
+Handle joined results with proper type awareness: when processing results, check whether joins were applied and access data accordingly. Use type assertions or conditional logic to handle different result shapes: `const paymentData = hasJoin ? result.payments : result`. Remember to apply numeric conversions to the correct nested properties and handle null values from outer joins appropriately.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/db_numeric_conversions.md b/agent/trpc_agent/knowledge_base/db_numeric_conversions.md
new file mode 100644
index 00000000..cbe5ecd4
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/db_numeric_conversions.md
@@ -0,0 +1,7 @@
+# Numeric Type Conversions
+
+PostgreSQL's `numeric` type returns string values through Drizzle ORM to preserve arbitrary precision, requiring explicit conversion in your handlers. When selecting data, use `parseFloat(row.price)` to convert string values back to JavaScript numbers. When inserting or updating, use `input.price.toString()` to convert numbers to strings for database storage. This conversion is essential for all `numeric` and `decimal` columns.
+
+Apply conversions consistently in your handlers: map over query results to convert all numeric fields, or handle conversions when building response objects. For example: `results.map(product => ({ ...product, price: parseFloat(product.price) }))`. Always convert every numeric field - partial conversions lead to type inconsistencies and runtime errors.
+
+Test numeric conversions explicitly in your unit tests: verify that returned values have the correct JavaScript type using `typeof result.price === 'number'`. Include both positive and negative numbers, zero values, and decimal precision in your test cases. Remember that `integer` and `real` Drizzle columns return native numbers and don't need conversion - only `numeric` columns require this handling.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/db_query_patterns.md b/agent/trpc_agent/knowledge_base/db_query_patterns.md
new file mode 100644
index 00000000..4d84aee5
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/db_query_patterns.md
@@ -0,0 +1,7 @@
+# Database Query Patterns
+
+Build queries step-by-step using Drizzle's chainable API, starting with `db.select().from(table)` and applying modifications in the correct order. Always use proper operators from 'drizzle-orm': `eq(table.column, value)` for equality, `gte()` for greater-than-or-equal, `desc(table.column)` for descending sort, and `isNull()` for null checks. Never use JavaScript comparison operators directly on table columns.
+
+Apply query modifications in the correct sequence: start with base query, add joins if needed, apply where conditions, then add ordering and pagination. For example: `query = query.where(conditions).orderBy(desc(table.created_at)).limit(10).offset(20)`. This order ensures type inference works correctly and prevents runtime errors.
+
+When building conditional queries, initialize the base query first, then apply filters conditionally using arrays of conditions. Use `and(...conditions)` with the spread operator when combining multiple conditions - never pass an array directly to `and()`. This pattern maintains clean code structure and proper TypeScript inference while supporting dynamic query building based on user input.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/frontend_form_handling.md b/agent/trpc_agent/knowledge_base/frontend_form_handling.md
new file mode 100644
index 00000000..2ff7e24c
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/frontend_form_handling.md
@@ -0,0 +1,7 @@
+# Form Handling Patterns
+
+Handle nullable database fields carefully in controlled inputs by providing defined values: use `value={formData.description || ''}` to convert null to empty string for display, then convert back with `description: e.target.value || null` when updating state. This pattern ensures HTML inputs always receive string values while maintaining proper null handling for database operations.
+
+Structure form state to match your schema types exactly, initializing nullable fields as null rather than undefined: `description: null, price: 0, stock_quantity: 0`. Use explicit TypeScript types for setState callbacks: `setFormData((prev: CreateProductInput) => ({ ...prev, field: value }))` to catch type mismatches early and ensure state consistency.
+
+Implement proper form validation and submission handling with loading states and error feedback. Reset forms after successful submission by restoring initial state values. For numeric inputs, use proper conversion: `parseInt(e.target.value) || 0` for integers and `parseFloat(e.target.value) || 0` for decimals, with appropriate min/max constraints and step values for better user experience.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/frontend_hooks.md b/agent/trpc_agent/knowledge_base/frontend_hooks.md
new file mode 100644
index 00000000..925356e3
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/frontend_hooks.md
@@ -0,0 +1,7 @@
+# React Hooks Patterns
+
+Follow React Hook rules strictly by including all dependencies in useEffect, useCallback, and useMemo dependency arrays. Wrap functions used in useEffect with useCallback if they reference state or props: `const loadData = useCallback(async () => { /* logic */ }, [dependency1, dependency2])`. Use empty dependency arrays `[]` only for mount-only effects that don't depend on any values.
+
+Structure hook usage with clear patterns: declare state first, then memoized values and callbacks, then effects that depend on them. For data fetching, create a memoized callback that handles the async operation, then use it in useEffect: `useEffect(() => { loadData(); }, [loadData])`. This pattern prevents infinite re-renders and makes dependencies explicit.
+
+Handle async operations properly by checking for cleanup in useEffect when dealing with component unmounting or dependency changes. Use loading states to provide user feedback during async operations, and handle errors gracefully by catching them in your async callbacks and updating error state appropriately. Never ignore the exhaustive-deps ESLint rule - missing dependencies cause bugs.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/frontend_react_components.md b/agent/trpc_agent/knowledge_base/frontend_react_components.md
new file mode 100644
index 00000000..cfec5d7f
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/frontend_react_components.md
@@ -0,0 +1,7 @@
+# React Component Organization
+
+Create separate components when logic exceeds 100 lines, when components are reused in multiple places, or when they have distinct responsibilities like ProductForm or ProductList. Organize files logically: shared UI components in `client/src/components/ui/`, feature-specific components as `client/src/components/FeatureName.tsx`, and complex features in subdirectories like `client/src/components/feature/`.
+
+Keep components focused on single responsibility and avoid mixing concerns. A ProductForm should handle form state and validation, while a ProductList should handle display and user interactions. Use composition over inheritance: build complex UIs by combining focused components rather than creating monolithic components with multiple responsibilities.
+
+Follow consistent patterns for component structure: props interface definition, state management, event handlers, then JSX return. Use explicit TypeScript types for all props, state, and event handlers. Import types separately from runtime imports using `import type` syntax to keep bundles clean and improve build performance.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/frontend_styling.md b/agent/trpc_agent/knowledge_base/frontend_styling.md
new file mode 100644
index 00000000..993d9fbd
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/frontend_styling.md
@@ -0,0 +1,7 @@
+# Frontend Styling Guidelines
+
+Use Tailwind CSS classes directly in JSX for most styling needs, avoiding custom CSS unless necessary for complex animations or reusable component styles. When using @apply, only use it within @layer components, never in @layer base, to maintain proper cascade behavior. Structure classes logically: layout properties first (flex, grid), then spacing (margin, padding), then visual properties (colors, borders).
+
+Adjust visual design to match the user's request context: use default professional styling for corporate business applications, but incorporate custom colors, emojis, and engaging visual elements for more playful or consumer-focused applications. Consider the mood and target audience when selecting color schemes, typography, and interactive elements.
+
+Maintain consistency across components by establishing design patterns early: consistent spacing scales, color usage, and component sizing. Use Tailwind's design system features like consistent spacing (space-4, space-8) and semantic color names (gray-500, blue-600) rather than arbitrary values. Create reusable component styles when you find yourself repeating complex class combinations across multiple components.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/frontend_trpc_integration.md b/agent/trpc_agent/knowledge_base/frontend_trpc_integration.md
new file mode 100644
index 00000000..d57e3f07
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/frontend_trpc_integration.md
@@ -0,0 +1,7 @@
+# tRPC Frontend Integration
+
+Use tRPC client for all backend communication with proper TypeScript inference from your server router types. Import the configured client and call queries with `await trpc.getProducts.query()` and mutations with `await trpc.createProduct.mutate(data)`. Store complete responses before accessing properties to ensure type safety and handle loading states appropriately.
+
+Handle tRPC responses correctly by matching the actual return types from your handlers. Don't assume field names or nested structures - inspect the handler implementation to verify the exact response format. Transform data after fetching if your components need different structures, but keep state types aligned with API responses to avoid confusion.
+
+Implement proper error handling for tRPC calls using try/catch blocks around queries and mutations. Display user-friendly error messages while logging detailed errors for debugging. Use loading states to provide feedback during async operations, and update component state immediately after successful mutations to keep the UI responsive and synchronized with the backend.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/frontend_type_imports.md b/agent/trpc_agent/knowledge_base/frontend_type_imports.md
new file mode 100644
index 00000000..aeae349e
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/frontend_type_imports.md
@@ -0,0 +1,7 @@
+# TypeScript Import Patterns
+
+Calculate relative paths carefully when importing server types: from `client/src/App.tsx` use `../../server/src/schema`, from `client/src/components/Component.tsx` use `../../../server/src/schema`, and from nested components add additional `../` segments. Count exactly: start from your file location, navigate up to client directory, then up to project root, then down to server directory.
+
+Always use type-only imports for server types: `import type { Product, CreateProductInput } from '../../server/src/schema'`. This prevents runtime imports of server code in your client bundle and clearly separates type definitions from runtime dependencies. Use regular imports only for client-specific code like components, utilities, and tRPC client configuration.
+
+Organize imports consistently: external dependencies first, then internal imports grouped by type (components, utilities, types), and finally relative imports from closest to furthest. Use trailing commas in import lists for better git diffs: `import { A, B, C, } from 'module'`. This pattern makes imports easy to scan and maintain as your project grows.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/handler_error_handling.md b/agent/trpc_agent/knowledge_base/handler_error_handling.md
new file mode 100644
index 00000000..7da7430a
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/handler_error_handling.md
@@ -0,0 +1,7 @@
+# Handler Error Handling
+
+Wrap database operations in try/catch blocks to handle connection issues, constraint violations, and other database errors gracefully. Log the complete error object with contextual information: `console.error('Product creation failed:', error)` provides debugging information without exposing sensitive details to clients. Always rethrow the original error to preserve stack traces and allow tRPC to handle error responses appropriately.
+
+Handle foreign key constraints proactively by validating referenced entities exist before attempting insert/update operations. This prevents "violates foreign key constraint" errors and provides clearer error messages. For example, verify a user exists before creating records that reference the user ID. This validation should happen in your business logic, not through database error handling.
+
+Avoid silent failures and ensure all error paths are explicit. Never return empty values or default objects to hide errors - this breaks the contract with your clients and makes debugging nearly impossible. Let database errors bubble up through tRPC's error handling system, which will provide appropriate HTTP status codes and error messages while protecting sensitive information.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/handler_implementation.md b/agent/trpc_agent/knowledge_base/handler_implementation.md
new file mode 100644
index 00000000..a072f083
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/handler_implementation.md
@@ -0,0 +1,7 @@
+# Handler Implementation
+
+Handlers should be focused async functions that accept parsed Zod input types and return clean domain objects. Structure handlers with clear imports, type annotations, and single responsibility: import your database connection, table schemas, and type definitions at the top, then implement the core logic with proper error handling. Export handlers as named exports to support easy testing and composition.
+
+Follow the pattern of accepting fully parsed input types with defaults already applied by Zod validation. Your handler signature should be `async (input: CreateProductInput): Promise` where the input type already includes default values and validation. This approach separates concerns: tRPC handles validation, handlers handle business logic.
+
+Keep handlers isolated and testable by avoiding dependencies on other handlers. Import only what you need: database connection, table schemas, and utility functions. Structure the implementation with clear steps: validate prerequisites (like foreign key existence), perform the database operation, handle numeric conversions for the response, and return properly typed results. This pattern makes handlers predictable and easy to debug.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/handler_testing.md b/agent/trpc_agent/knowledge_base/handler_testing.md
new file mode 100644
index 00000000..9d7790e5
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/handler_testing.md
@@ -0,0 +1,7 @@
+# Handler Testing
+
+Create reliable test setup using `beforeEach(createDB)` and `afterEach(resetDB)` to ensure clean database state between tests. Never use mocks - always test against real database operations to catch integration issues, constraint violations, and type conversion problems. Create prerequisite data first (users, categories) before testing dependent records to avoid foreign key constraint errors.
+
+Include ALL fields in test inputs, even those with Zod defaults, to ensure your tests represent realistic usage patterns. Test numeric conversions explicitly by verifying `typeof result.price === 'number'` to catch string-to-number conversion issues. Use flexible error assertions like `expect().rejects.toThrow(/pattern/i)` to handle different error message formats across database drivers.
+
+Structure tests with clear arrange-act-assert patterns: set up test data, call the handler, verify results including field types and database state. Test both success and failure scenarios, focusing on business logic validation rather than framework behavior. Keep tests isolated by never calling other handlers within tests - create test data directly in the database when needed for complex scenarios.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/schema_nullable_optional.md b/agent/trpc_agent/knowledge_base/schema_nullable_optional.md
new file mode 100644
index 00000000..f8579bf9
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/schema_nullable_optional.md
@@ -0,0 +1,7 @@
+# Nullable vs Optional Fields
+
+Use `.nullable()` when a field can be explicitly null in the database - this means the field is always present but can contain a null value. Use `.optional()` when a field can be omitted entirely from the input object. For database fields with defaults, use `.optional()` in input schemas since users don't need to provide values for fields that have defaults.
+
+The distinction is crucial for form handling and API design. Nullable fields like `description: z.string().nullable()` expect either a string value or explicit null, while optional fields like `category: z.string().optional()` can be completely omitted from the request payload. For update operations, combine both: `description: z.string().nullable().optional()` allows the field to be omitted (no change) or explicitly set to null or a string value.
+
+Never use `.nullish()` as it conflates these two distinct concepts and makes your API less predictable. Be explicit about your intentions: if a field can be null in the database, mark it as nullable; if it can be omitted from user input, mark it as optional. This clarity prevents confusion and makes your schemas self-documenting.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/schema_numeric_types.md b/agent/trpc_agent/knowledge_base/schema_numeric_types.md
new file mode 100644
index 00000000..50b4d3bc
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/schema_numeric_types.md
@@ -0,0 +1,7 @@
+# Numeric Type Handling
+
+PostgreSQL's `numeric` type preserves precision by returning string values through Drizzle ORM, while `integer` and `real` types return native JavaScript numbers. Always define Zod schemas with `z.number()` for all numeric database columns, regardless of the underlying type - handle string-to-number conversions in your handlers, not in schemas. Use `z.number().int()` for integer fields to enforce whole number validation.
+
+For decimal/monetary values stored as `numeric` columns, your handlers must convert between strings and numbers: use `toString()` when inserting/updating and `parseFloat()` when selecting data. This conversion is critical because PostgreSQL's numeric type maintains arbitrary precision by avoiding floating-point representation, but JavaScript works with native numbers.
+
+Validation constraints should be applied at the schema level: use `.positive()` for prices, `.nonnegative()` for quantities, and `.int()` for count fields. These validations catch invalid data before it reaches your handlers, providing clear error messages to clients and preventing database constraint violations.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/schema_type_alignment.md b/agent/trpc_agent/knowledge_base/schema_type_alignment.md
new file mode 100644
index 00000000..43323943
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/schema_type_alignment.md
@@ -0,0 +1,7 @@
+# Schema Type Alignment
+
+Critical alignment between Zod and Drizzle types is essential for type safety. Drizzle fields with `.notNull()` should NOT have `.nullable()` in Zod, while Drizzle fields without `.notNull()` MUST have `.nullable()` in Zod schemas. Never use `.nullish()` in Zod - always be explicit with `.nullable()` or `.optional()` based on your database schema.
+
+For numeric types, remember that Drizzle `numeric()` columns return STRING values from PostgreSQL to preserve precision, while `real()` and `integer()` return native numbers. Always define Zod schemas with `z.number()` for ALL numeric column types regardless of the underlying database representation - handle conversions in your handlers, not in schemas.
+
+Date handling requires special attention: use `z.coerce.date()` for Drizzle `timestamp()` fields to automatically convert string timestamps to Date objects. For enum fields, create matching Zod enums with `z.enum([...])` and never accept raw strings - always validate against the defined enum values to prevent runtime errors.
\ No newline at end of file
diff --git a/agent/trpc_agent/knowledge_base/schema_zod_basics.md b/agent/trpc_agent/knowledge_base/schema_zod_basics.md
new file mode 100644
index 00000000..f8e99685
--- /dev/null
+++ b/agent/trpc_agent/knowledge_base/schema_zod_basics.md
@@ -0,0 +1,7 @@
+# Zod Schema Basics
+
+Zod schemas define the structure and validation rules for your data types. Always define schemas using consistent patterns with proper type inference. Create a dedicated schema file (typically `server/src/schema.ts`) to centralize all type definitions, and use `z.infer` to generate TypeScript types from schemas.
+
+The basic pattern involves creating object schemas with appropriate field validations, then exporting both the schema and its inferred type. For example, `z.string()` for text fields, `z.number()` for numeric values, and `z.coerce.date()` for timestamp fields that need automatic conversion from strings to Date objects. Always validate constraints like `.positive()` for prices or `.int()` for integer-only fields.
+
+Export clean, focused schemas that match your business domain. Create separate schemas for different operations (create vs update inputs) to handle optional fields properly. Keep schemas simple and avoid complex nested validations - prefer composition over deeply nested structures for maintainability.
\ No newline at end of file
diff --git a/agent/trpc_agent/playbooks.py b/agent/trpc_agent/playbooks.py
index 67e73f7a..f4378a7b 100644
--- a/agent/trpc_agent/playbooks.py
+++ b/agent/trpc_agent/playbooks.py
@@ -1,3 +1,6 @@
+# Concise system prompts for tRPC agent
+# These are much shorter prompts that rely on knowledge base enrichment for detailed guidance
+
# Tool usage rules for all TRPC prompts
TOOL_USAGE_RULES = """
# File Management Tools
@@ -38,545 +41,17 @@
- For maximum efficiency, invoke multiple tools simultaneously when performing independent operations
"""
-BASE_TYPESCRIPT_SCHEMA = """
-
-import { z } from 'zod';
-
-// Product schema with proper numeric handling
-export const productSchema = z.object({
- id: z.number(),
- name: z.string(),
- description: z.string().nullable(), // Nullable field, not optional (can be explicitly null)
- price: z.number(), // Stored as numeric in DB, but we use number in TS
- stock_quantity: z.number().int(), // Ensures integer values only
- created_at: z.coerce.date() // Automatically converts string timestamps to Date objects
-});
-
-export type Product = z.infer;
-
-// Input schema for creating products
-export const createProductInputSchema = z.object({
- name: z.string(),
- description: z.string().nullable(), // Explicit null allowed, undefined not allowed
- price: z.number().positive(), // Validate that price is positive
- stock_quantity: z.number().int().nonnegative() // Validate that stock is non-negative integer
-});
-
-export type CreateProductInput = z.infer;
-
-// Input schema for updating products
-export const updateProductInputSchema = z.object({
- id: z.number(),
- name: z.string().optional(), // Optional = field can be undefined (omitted)
- description: z.string().nullable().optional(), // Can be null or undefined
- price: z.number().positive().optional(),
- stock_quantity: z.number().int().nonnegative().optional()
-});
-
-export type UpdateProductInput = z.infer;
-
-""".strip()
-
-
-BASE_DRIZZLE_SCHEMA = """
-
-import { serial, text, pgTable, timestamp, numeric, integer } from 'drizzle-orm/pg-core';
-
-export const productsTable = pgTable('products', {
- id: serial('id').primaryKey(),
- name: text('name').notNull(),
- description: text('description'), // Nullable by default, matches Zod schema
- price: numeric('price', { precision: 10, scale: 2 }).notNull(), // Use numeric for monetary values with precision
- stock_quantity: integer('stock_quantity').notNull(), // Use integer for whole numbers
- created_at: timestamp('created_at').defaultNow().notNull(),
-});
-
-// TypeScript type for the table schema
-export type Product = typeof productsTable.$inferSelect; // For SELECT operations
-export type NewProduct = typeof productsTable.$inferInsert; // For INSERT operations
-
-// Important: Export all tables and relations for proper query building
-export const tables = { products: productsTable };
-
-""".strip()
-
-
-BASE_HANDLER_DECLARATION = """
-
-import { type CreateProductInput, type Product } from '../schema';
-
-export const async createProduct(input: CreateProductInput): Promise => {
- // This is a placeholder declaration! Real code should be implemented here.
- // The goal of this handler is creating a new product persisting it in the database.
- return Promise.resolve({
- id: 0, // Placeholder ID
- name: input.name,
- description: input.description || null, // Handle nullable field
- price: input.price,
- stock_quantity: input.stock_quantity,
- created_at: new Date() // Placeholder date
- } as Product
- )
-}
-
-
-""".strip()
-
-BASE_GET_HANDLER_DECLARATION = """
-
-import { type Product } from '../schema';
-
-export const async getProducts(): Promise => {
- // This is a placeholder declaration! Real code should be implemented here.
- // The goal of this handler is fetching all products from the database.
- return [];
-}
-
-""".strip()
-
-
-BASE_HANDLER_IMPLEMENTATION = """
-
-import { db } from '../db';
-import { productsTable } from '../db/schema';
-import { type CreateProductInput, type Product } from '../schema';
-
-export const createProduct = async (input: CreateProductInput): Promise => {
- try {
- // Insert product record
- const result = await db.insert(productsTable)
- .values({
- name: input.name,
- description: input.description,
- price: input.price.toString(), // Convert number to string for numeric column
- stock_quantity: input.stock_quantity // Integer column - no conversion needed
- })
- .returning()
- .execute();
-
- // Convert numeric fields back to numbers before returning
- const product = result[0];
- return {
- ...product,
- price: parseFloat(product.price) // Convert string back to number
- };
- } catch (error) {
- console.error('Product creation failed:', error);
- throw error;
- }
-};
-
-""".strip()
-
-
-BASE_HANDLER_TEST = """
-
-import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
-import { resetDB, createDB } from '../helpers';
-import { db } from '../db';
-import { productsTable } from '../db/schema';
-import { type CreateProductInput } from '../schema';
-import { createProduct } from '../handlers/create_product';
-import { eq, gte, between, and } from 'drizzle-orm';
-
-// Simple test input
-const testInput: CreateProductInput = {
- name: 'Test Product',
- description: 'A product for testing',
- price: 19.99,
- stock_quantity: 100
-};
-
-describe('createProduct', () => {
- beforeEach(createDB);
- afterEach(resetDB);
-
- it('should create a product', async () => {
- const result = await createProduct(testInput);
-
- // Basic field validation
- expect(result.name).toEqual('Test Product');
- expect(result.description).toEqual(testInput.description);
- expect(result.price).toEqual(19.99);
- expect(result.stock_quantity).toEqual(100);
- expect(result.id).toBeDefined();
- expect(result.created_at).toBeInstanceOf(Date);
- });
-
- it('should save product to database', async () => {
- const result = await createProduct(testInput);
-
- // Query using proper drizzle syntax
- const products = await db.select()
- .from(productsTable)
- .where(eq(productsTable.id, result.id))
- .execute();
-
- expect(products).toHaveLength(1);
- expect(products[0].name).toEqual('Test Product');
- expect(products[0].description).toEqual(testInput.description);
- expect(parseFloat(products[0].price)).toEqual(19.99);
- expect(products[0].created_at).toBeInstanceOf(Date);
- });
-
- it('should query products by date range correctly', async () => {
- // Create test product
- await createProduct(testInput);
-
- // Test date filtering - demonstration of correct date handling
- const today = new Date();
- const tomorrow = new Date(today);
- tomorrow.setDate(tomorrow.getDate() + 1);
-
- // Proper query building - step by step
- let query = db.select()
- .from(productsTable);
-
- // Apply date filter - Date objects work directly with timestamp columns
- query = query.where(
- and([
- gte(productsTable.created_at, today),
- between(productsTable.created_at, today, tomorrow)
- ])
- );
-
- const products = await query.execute();
-
- expect(products.length).toBeGreaterThan(0);
- products.forEach(product => {
- expect(product.created_at).toBeInstanceOf(Date);
- expect(product.created_at >= today).toBe(true);
- expect(product.created_at <= tomorrow).toBe(true);
- });
- });
-});
-
-""".strip()
-
-
-BASE_SERVER_INDEX = """
-
-import { initTRPC } from '@trpc/server';
-import { createHTTPServer } from '@trpc/server/adapters/standalone';
-import 'dotenv/config';
-import cors from 'cors';
-import superjson from 'superjson';
-
-const t = initTRPC.create({
- transformer: superjson,
-});
-
-const publicProcedure = t.procedure;
-const router = t.router;
-
-const appRouter = router({
- healthcheck: publicProcedure.query(() => {
- return { status: 'ok', timestamp: new Date().toISOString() };
- }),
-});
-
-export type AppRouter = typeof appRouter;
-
-async function start() {
- const port = process.env['SERVER_PORT'] || 2022;
- const server = createHTTPServer({
- middleware: (req, res, next) => {
- cors()(req, res, next);
- },
- router: appRouter,
- createContext() {
- return {};
- },
- });
- server.listen(port);
- console.log(`TRPC server listening at port: ${port}`);
-}
-
-start();
-
-""".strip()
-
-
-BASE_APP_TSX = """
-
-import { Button } from '@/components/ui/button';
-import { Input } from '@/components/ui/input';
-import { trpc } from '@/utils/trpc';
-import { useState, useEffect, useCallback } from 'react';
-// Using type-only import for better TypeScript compliance
-import type { Product, CreateProductInput } from '../../server/src/schema';
-
-function App() {
- // Explicit typing with Product interface
- const [products, setProducts] = useState([]);
- const [isLoading, setIsLoading] = useState(false);
-
- // Form state with proper typing for nullable fields
- const [formData, setFormData] = useState({
- name: '',
- description: null, // Explicitly null, not undefined
- price: 0,
- stock_quantity: 0
- });
-
- // useCallback to memoize function used in useEffect
- const loadProducts = useCallback(async () => {
- try {
- const result = await trpc.getProducts.query();
- setProducts(result);
- } catch (error) {
- console.error('Failed to load products:', error);
- }
- }, []); // Empty deps since trpc is stable
-
- // useEffect with proper dependencies
- useEffect(() => {
- loadProducts();
- }, [loadProducts]);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setIsLoading(true);
- try {
- const response = await trpc.createProduct.mutate(formData);
- // Update products list with explicit typing in setState callback
- setProducts((prev: Product[]) => [...prev, response]);
- // Reset form
- setFormData({
- name: '',
- description: null,
- price: 0,
- stock_quantity: 0
- });
- } catch (error) {
- console.error('Failed to create product:', error);
- } finally {
- setIsLoading(false);
- }
- };
-
- return (
-
- );
-}
-
-export default App;
-
-""".strip()
-
-
-BASE_COMPONENT_EXAMPLE = """
-
-import { Input } from '@/components/ui/input';
-import { Button } from '@/components/ui/button';
-import { useState } from 'react';
-// Note the extra ../ because we're in components subfolder
-import type { CreateProductInput } from '../../../server/src/schema';
-
-interface ProductFormProps {
- onSubmit: (data: CreateProductInput) => Promise;
- isLoading?: boolean;
-}
-
-export function ProductForm({ onSubmit, isLoading = false }: ProductFormProps) {
- const [formData, setFormData] = useState({
- name: '',
- description: null,
- price: 0,
- stock_quantity: 0
- });
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- await onSubmit(formData);
- // Reset form after successful submission
- setFormData({
- name: '',
- description: null,
- price: 0,
- stock_quantity: 0
- });
- };
-
- return (
-
- );
-}
-
-""".strip()
-
-TRPC_INDEX_SHIM = """
-...
-import { createProductInputSchema } from './schema';
-import { createProduct } from './handlers/create_product';
-import { getProducts } from './handlers/get_products';
-...
-const appRouter = router({
- createProduct: publicProcedure
- .input(createProductInputSchema)
- .mutation(({ input }) => createProduct(input)),
- getProducts: publicProcedure
- .query(() => getProducts()),
-});
-...
-""".strip()
-
-TYPE_ALIGNMENT_RULES_SCHEMA = """# CRITICAL Type Alignment Rules for Schema Definition:
-1. Align Zod and Drizzle types exactly:
- - Drizzle `.notNull()` → Zod should NOT have `.nullable()`
- - Drizzle field without `.notNull()` → Zod MUST have `.nullable()`
- - Never use `.nullish()` in Zod - use `.nullable()` or `.optional()` as appropriate
-
-2. Numeric type definitions:
- - CRITICAL: Drizzle `numeric()` type returns STRING values from PostgreSQL (to preserve precision)
- - Drizzle `real()` and `integer()` return native number values from the database
- - Define your Zod schema with `z.number()` for ALL numeric column types
- - For integer values, use `z.number().int()` for proper validation
-
-3. Date handling in schemas:
- - For Drizzle `timestamp()` fields → Use Zod `z.coerce.date()`
- - For Drizzle `date()` fields → Use Zod `z.string()` with date validation
-
-4. Enum handling:
- - For Drizzle `pgEnum()` → Create matching Zod enum with `z.enum([...])`
- - NEVER accept raw string for enum fields, always validate against enum values
-
-5. Optional vs Nullable:
- - Use `.nullable()` when a field can be explicitly null
- - Use `.optional()` when a field can be omitted entirely
- - For DB fields with defaults, use `.optional()` in input schemas
-
-6. Type exports:
- - Export types for ALL schemas using `z.infer`
- - Create both input and output schema types for handlers
-"""
-
BACKEND_DRAFT_SYSTEM_PROMPT = f"""
-You are software engineer, follow those rules:
-
-- Define all types using zod in a single file server/src/schema.ts
-- Always define schema and corresponding type using z.infer
-Example:
-{BASE_TYPESCRIPT_SCHEMA}
-
-- Define all database tables using drizzle-orm in server/src/db/schema.ts
-- IMPORTANT: Always export all tables to enable relation queries
-Example:
-{BASE_DRIZZLE_SCHEMA}
-
-- For each handler write its dummy stub implementations in corresponding file in server/src/handlers/; prefer simple handlers, follow single responsibility principle, add comments that reflect the purpose of the handler for the future implementation.
-Examples:
-{BASE_HANDLER_DECLARATION}
+You are a software engineer generating tRPC TypeScript backend code.
-{BASE_GET_HANDLER_DECLARATION}
+Core responsibilities:
+- Define Zod schemas in server/src/schema.ts with proper types using z.infer
+- Define Drizzle database tables in server/src/db/schema.ts (always export tables)
+- Create handler stubs in server/src/handlers/ with single responsibility
+- Generate tRPC router in server/src/index.ts with proper imports
-- Generate root TRPC index file in server/src/index.ts
-Example:
-{BASE_SERVER_INDEX}
-
-# Relevant parts to modify:
-- Imports of handlers and schema types
-- Registering TRPC routes
-{TRPC_INDEX_SHIM}
-
-{TYPE_ALIGNMENT_RULES_SCHEMA}
-
-Keep the things simple and do not create entities that are not explicitly required by the task.
-Make sure to follow the best software engineering practices, write structured and maintainable code.
-Even stupid requests should be handled professionally - build precisely the app that user needs, keeping its quality high.
+Follow best practices: structured code, proper typing, nullable vs optional alignment between Zod and Drizzle.
+Build exactly what's requested with high quality.
{TOOL_USAGE_RULES}
""".strip()
@@ -592,174 +67,21 @@
{{user_prompt}}
""".strip()
-
-DATABASE_PATTERNS = """
-## Numeric Type Conversions:
-- For `numeric()` columns: Always use `parseFloat()` when returning data, `toString()` when inserting
-- Example conversions:
- ```typescript
- // When selecting data with numeric columns:
- const results = await db.select().from(productsTable).execute();
- return results.map(product => ({{
- ...product,
- price: parseFloat(product.price), // Convert string to number
- amount: parseFloat(product.amount) // Convert ALL numeric fields
- }}));
-
- // When inserting/updating numeric columns:
- await db.insert(productsTable).values({
- ...input,
- price: input.price.toString(), // Convert number to string
- amount: input.amount.toString() // Convert ALL numeric fields
- });
- ```
-
-## Database Query Patterns:
-- CRITICAL: Maintain proper type inference when building queries conditionally
-- Always build queries step-by-step, applying `.where()` before `.limit()`, `.offset()`, or `.orderBy()`
-- For conditional queries, initialize the query first, then apply filters conditionally
-- When filtering with multiple conditions, collect conditions in an array and apply `.where(and(...conditions))` with spread operator
-- NEVER use `and(conditions)` - ALWAYS use `and(...conditions)` with the spread operator!
-- ALWAYS use the proper operators from 'drizzle-orm':
- - Use eq(table.column, value) instead of table.column === value
- - Use and(...conditions) with SPREAD operator, not and(conditions)
- - Use isNull(table.column), not table.column === null
- - Use desc(table.column) for descending order
-
-- Example pattern for conditional filters:
- ```typescript
- // Good pattern for conditional filters
- let query = db.select().from(productsTable);
-
- const conditions: SQL[] = [];
-
- if (filters.minPrice !== undefined) {
- conditions.push(gte(productsTable.price, filters.minPrice));
- }
-
- if (filters.category) {
- conditions.push(eq(productsTable.category, filters.category));
- }
-
- if (conditions.length > 0) {
- query = query.where(conditions.length === 1 ? conditions[0] : and(...conditions)); // SPREAD the array!
- }
-
- // Apply other query modifiers AFTER where clause
- if (orderBy) {
- query = query.orderBy(desc(productsTable[orderBy]));
- }
-
- // Apply pagination LAST
- query = query.limit(limit).offset(offset);
-
- const results = await query.execute();
- ```
-
-- Handle joined data structures correctly - results change shape after joins:
- ```typescript
- // After a join, results become nested objects
- const results = await db.select()
- .from(paymentsTable)
- .innerJoin(subscriptionsTable, eq(paymentsTable.subscription_id, subscriptionsTable.id))
- .execute();
-
- // Access data from the correct nested property
- return results.map(result => ({
- id: result.payments.id,
- amount: parseFloat(result.payments.amount), // Note: numeric conversion!
- subscription_name: result.subscriptions.name
- }));
- ```
-
-- Pattern for queries with joins:
- ```typescript
- // Base query without join
- let baseQuery = db.select().from(paymentsTable);
-
- // Apply join conditionally (changes result structure!)
- if (filters.user_id) {
- baseQuery = baseQuery.innerJoin(
- subscriptionsTable,
- eq(paymentsTable.subscription_id, subscriptionsTable.id)
- );
- }
-
- // Build conditions array
- const conditions: SQL[] = [];
- if (filters.user_id) {
- conditions.push(eq(subscriptionsTable.user_id, filters.user_id));
- }
-
- // Apply where clause
- const query = conditions.length > 0
- ? baseQuery.where(and(...conditions))
- : baseQuery;
-
- const results = await query.execute();
-
- // Handle different result structures based on join
- return results.map(result => {
- // If joined, data is nested: { payments: {...}, subscriptions: {...} }
- const paymentData = filters.user_id
- ? (result as any).payments
- : result;
-
- return {
- ...paymentData,
- amount: parseFloat(paymentData.amount) // Don't forget numeric conversion!
- };
- });
- ```
-""".strip()
-
BACKEND_HANDLER_SYSTEM_PROMPT = f"""
-- Write implementation for the handler function
-- Write small but meaningful test set for the handler
-
-Example Handler:
-{BASE_HANDLER_IMPLEMENTATION}
-
-Example Test:
-{BASE_HANDLER_TEST}
-
-# Implementation Rules:
-{DATABASE_PATTERNS}
-
-## Testing Best Practices:
-- Create reliable test setup: Use `beforeEach(createDB)` and `afterEach(resetDB)`
-- Create prerequisite data first (users, categories) before dependent records
-- Use flexible error assertions: `expect().rejects.toThrow(/pattern/i)`
-- Include ALL fields in test inputs, even those with Zod defaults
-- Test numeric conversions: verify `typeof result.price === 'number'`
-- CRITICAL handler type signatures:
- ```typescript
- // Handler should expect the PARSED Zod type (with defaults applied)
- export const searchProducts = async (input: SearchProductsInput): Promise => {{
- // input.limit and input.offset are guaranteed to exist here
- // because Zod has already parsed and applied defaults
- }};
+You are implementing tRPC handler functions with proper testing.
- // If you need a handler that accepts pre-parsed input,
- // create a separate input type without defaults
- ```
+Core tasks:
+- Write complete handler implementation with database operations
+- Write meaningful test suite with setup/teardown
+- Handle parsed Zod types (defaults already applied)
+- Use proper database patterns and error handling
-# Common Pitfalls to Avoid:
-1. **Numeric columns**: Always use parseFloat() when selecting, toString() when inserting float/decimal values as they are stored as numerics in PostgreSQL and later converted to strings in Drizzle ORM
-2. **Query conditions**: Use and(...conditions) with spread operator, NOT and(conditions)
-3. **Joined results**: Access data via nested properties (result.table1.field, result.table2.field)
-4. **Test inputs**: Include ALL fields in test inputs, even those with Zod defaults
-5. **Type annotations**: Use SQL[] for condition arrays
-6. **Query order**: Always apply .where() before .limit(), .offset(), or .orderBy()
-7. **Foreign key validation**: For INSERT/UPDATE operations with foreign keys, verify referenced entities exist first to prevent "violates foreign key constraint" errors. Ensure tests cover the use case where foreign keys are used.
-
-# Error Handling Best Practices:
-- Wrap database operations in try/catch blocks
-- Log the full error object with context: `console.error('Operation failed:', error);`
-- Rethrow original errors to preserve stack traces: `throw error;`
-- Error handling does not need to be tested in unit tests
-- Do not use other handlers in implementation or tests - keep fully isolated
-- NEVER use mocks - always test against real database operations
+Key points:
+- Handlers expect fully parsed Zod input types
+- Always test database operations (no mocks)
+- Validate foreign keys exist before referencing
+- Handle numeric conversions: parseFloat() for selects, toString() for inserts
+- Keep handlers isolated - no cross-handler dependencies
{TOOL_USAGE_RULES}
""".strip()
@@ -767,110 +89,29 @@
BACKEND_HANDLER_USER_PROMPT = """
Key project files:
{{project_context}}
-{% if feedback_data %}
-Task:
-{{ feedback_data }}
-{% endif %}
Use the tools to create or modify the handler implementation and test files.
-""".strip()
-
-
-FRONTEND_SYSTEM_PROMPT = f"""You are software engineer, follow those rules:
-- Generate react frontend application using radix-ui components.
-- Backend communication is done via tRPC.
-- Use Tailwind CSS for styling. Use Tailwind classes directly in JSX. Avoid using @apply unless you need to create reusable component styles. When using @apply, only use it in @layer components, never in @layer base.
-
-Example App Component:
-{BASE_APP_TSX}
-Example Nested Component (showing import paths):
-{BASE_COMPONENT_EXAMPLE}
-
-# Component Organization Guidelines:
-- Create separate components when:
- - Logic becomes complex (>100 lines)
- - Component is reused in multiple places
- - Component has distinct responsibility (e.g., ProductForm, ProductList)
-- File structure:
- - Shared UI components: `client/src/components/ui/`
- - Feature components: `client/src/components/FeatureName.tsx`
- - Complex features: `client/src/components/feature/FeatureName.tsx`
-- Keep components focused on single responsibility
-
-For the visual aspect, adjust the CSS to match the user prompt to keep the design consistent with the original request in terms of overall mood. E.g. for serious corporate business applications, default CSS is great; for more playful or nice applications, use custom colors, emojis, and other visual elements to make it more engaging.
+Task:
+{{user_prompt}}
+""".strip()
-If and only if user prompt requires specific integration that can't be supported, it is acceptable to use some stubs (dummy data). Stub implementations should be clearly marked as such in code comments, and the user should be informed about them. Do your best to avoid stubs. Frontend should reflect the stub usage, so that the user can see that the stub is used for a specific purpose.
+FRONTEND_SYSTEM_PROMPT = f"""
+You are building React frontend with tRPC integration.
-- ALWAYS calculate the correct relative path when importing from server:
- - From `client/src/App.tsx` → use `../../server/src/schema` (2 levels up)
- - From `client/src/components/Component.tsx` → use `../../../server/src/schema` (3 levels up)
- - From `client/src/components/nested/Component.tsx` → use `../../../../server/src/schema` (4 levels up)
- - Count EXACTLY: start from your file location, go up to reach client/, then up to project root, then down to server/
+Core requirements:
+- Use React with radix-ui components and Tailwind CSS
+- Communicate with backend via tRPC
+- Organize components with single responsibility
+- Use correct relative paths for server imports
- Always use type-only imports: `import type {{ Product }} from '../../server/src/schema'`
-# CRITICAL: TypeScript Type Matching & API Integration
-- ALWAYS inspect the actual handler implementation to verify return types:
- - Use read_file on the handler file to see the exact return structure
- - Don't assume field names or nested structures
- - Example: If handler returns `Product[]`, don't expect `ProductWithSeller[]`
-- When API returns different type than needed for components:
- - Transform data after fetching, don't change the state type
- - Example: If API returns `Product[]` but component needs `ProductWithSeller[]`:
- ```typescript
- const products = await trpc.getUserProducts.query();
- const productsWithSeller = products.map(p => ({{
- ...p,
- seller: {{ id: user.id, name: user.name }}
- }}));
- ```
-- For tRPC queries, store the complete response before using properties
-- Access nested data correctly based on server's actual return structure
-
-# Syntax & Common Errors:
-- Double-check JSX syntax:
- - Type annotations: `onChange={{(e: React.ChangeEvent) => ...}}`
- - Import lists need proper commas: `import {{ A, B, C }} from ...`
- - Component names have no spaces: `AlertDialogFooter` not `AlertDialog Footer`
-- Handle nullable values in forms correctly:
- - For controlled inputs, always provide a defined value: `value={{formData.field || ''}}`
- - For nullable database fields, convert empty strings to null before submission:
- ```typescript
- onChange={{(e) => setFormData(prev => ({{
- ...prev,
- description: e.target.value || null // Empty string → null
- }})}}
- ```
- - For select/dropdown components, use meaningful defaults: `value={{filter || 'all'}}` not empty string
- - HTML input elements require string values, so convert null → '' for display, '' → null for storage
-- State initialization should match API return types exactly
-
-# TypeScript Best Practices:
-- Always provide explicit types for all callbacks:
- - useState setters: `setData((prev: DataType) => ...)`
- - Event handlers: `onChange={{(e: React.ChangeEvent) => ...}}`
- - Array methods: `items.map((item: ItemType) => ...)`
-- For numeric values and dates from API:
- - Frontend receives proper number types - no additional conversion needed
- - Use numbers directly: `product.price.toFixed(2)` for display formatting
- - Date objects from backend can be used directly: `date.toLocaleDateString()`
-- NEVER use mock data or hardcoded values - always fetch real data from the API
-
-# React Hook Dependencies:
-- Follow React Hook rules strictly:
- - Include all dependencies in useEffect/useCallback/useMemo arrays
- - Wrap functions used in useEffect with useCallback if they use state/props
- - Use empty dependency array `[]` only for mount-only effects
- - Example pattern:
- ```typescript
- const loadData = useCallback(async () => {{
- // data loading logic
- }}, [dependency1, dependency2]);
-
- useEffect(() => {{
- loadData();
- }}, [loadData]);
- ```
+Key practices:
+- Match frontend types exactly with handler return types
+- Handle nullable values properly in forms (null ↔ empty string conversion)
+- Follow React hooks rules with proper dependencies
+- Never use mock data - always fetch real data from API
+- Apply consistent styling that matches the requested mood/design
{TOOL_USAGE_RULES}
""".strip()
@@ -885,6 +126,33 @@
{{user_prompt}}
""".strip()
+EDIT_ACTOR_SYSTEM_PROMPT = f"""
+You are making targeted changes to a tRPC full-stack application.
+
+Core capabilities:
+- Modify React frontend (radix-ui + Tailwind CSS)
+- Edit tRPC backend handlers and schemas
+- Update database schemas with Drizzle ORM
+- Fix integration issues between frontend and backend
+
+Key principles:
+- Make only the changes requested in the feedback
+- Use correct relative paths for server imports
+- Maintain type safety between frontend and backend
+- Follow existing code patterns and conventions
+
+{TOOL_USAGE_RULES}
+""".strip()
+
+EDIT_ACTOR_USER_PROMPT = """
+{{ project_context }}
+
+Use the tools to create or modify files as needed and install required packages.
+Given original user request:
+{{ user_prompt }}
+Implement solely the required changes according to the user feedback:
+{{ feedback }}
+""".strip()
FRONTEND_VALIDATION_PROMPT = """Given the attached screenshot, decide where the frontend code is correct and relevant to the original prompt. Keep in mind that the backend is currently not implemented, so you can only validate the frontend code and ignore the backend part.
Original prompt to generate this website: {{ user_prompt }}.
@@ -906,6 +174,7 @@
the website looks okay, but displays database connection error. Given it is not frontend-related, I should answer yesyes
"""
+
FULL_UI_VALIDATION_PROMPT = """Given the attached screenshot and browser logs, decide where the app is correct and working.
{% if user_prompt %} User prompt: {{ user_prompt }} {% endif %}
Console logs from the browsers:
@@ -924,196 +193,4 @@
Example 3:
the website looks validyes
-"""
-
-
-EDIT_ACTOR_SYSTEM_PROMPT = f"""
-You are software engineer.
-
-Working with frontend follow these rules:
-- Generate react frontend application using radix-ui components.
-- Backend communication is done via tRPC.
-- Use Tailwind CSS for styling. Use Tailwind classes directly in JSX. Avoid using @apply unless you need to create reusable component styles. When using @apply, only use it in @layer components, never in @layer base.
-
-Example App Component:
-{BASE_APP_TSX}
-
-Example Nested Component (showing import paths):
-{BASE_COMPONENT_EXAMPLE}
-
-# Component Organization Guidelines:
-- Create separate components when:
- - Logic becomes complex (>100 lines)
- - Component is reused in multiple places
- - Component has distinct responsibility (e.g., ProductForm, ProductList)
-- File structure:
- - Shared UI components: `client/src/components/ui/`
- - Feature components: `client/src/components/FeatureName.tsx`
- - Complex features: `client/src/components/feature/FeatureName.tsx`
-- Keep components focused on single responsibility
-
-For the visual aspect, adjust the CSS to match the user prompt to keep the design consistent with the original request in terms of overall mood. E.g. for serious corporate business applications, default CSS is great; for more playful or nice applications, use custom colors, emojis, and other visual elements to make it more engaging.
-
-- ALWAYS calculate the correct relative path when importing from server:
- - From `client/src/App.tsx` → use `../../server/src/schema` (2 levels up)
- - From `client/src/components/Component.tsx` → use `../../../server/src/schema` (3 levels up)
- - From `client/src/components/nested/Component.tsx` → use `../../../../server/src/schema` (4 levels up)
- - Count EXACTLY: start from your file location, go up to reach client/, then up to project root, then down to server/
-- Always use type-only imports: `import type {{ Product }} from '../../server/src/schema'`
-
-# CRITICAL: TypeScript Type Matching & API Integration
-- ALWAYS inspect the actual handler implementation to verify return types:
- - Use read_file on the handler file to see the exact return structure
- - Don't assume field names or nested structures
- - Example: If handler returns `Product[]`, don't expect `ProductWithSeller[]`
-- When API returns different type than needed for components:
- - Transform data after fetching, don't change the state type
- - Example: If API returns `Product[]` but component needs `ProductWithSeller[]`:
- ```typescript
- const products = await trpc.getUserProducts.query();
- const productsWithSeller = products.map(p => ({{
- ...p,
- seller: {{ id: user.id, name: user.name }}
- }}));
- ```
-- For tRPC queries, store the complete response before using properties
-- Access nested data correctly based on server's actual return structure
-
-# Syntax & Common Errors:
-- Double-check JSX syntax:
- - Type annotations: `onChange={{(e: React.ChangeEvent) => ...}}`
- - Import lists need proper commas: `import {{ A, B, C }} from ...`
- - Component names have no spaces: `AlertDialogFooter` not `AlertDialog Footer`
-- Handle nullable values in forms correctly:
- - For controlled inputs, always provide a defined value: `value={{formData.field || ''}}`
- - For nullable database fields, convert empty strings to null before submission:
- ```typescript
- onChange={{(e) => setFormData(prev => ({{
- ...prev,
- description: e.target.value || null // Empty string → null
- }})}}
- ```
- - For select/dropdown components, use meaningful defaults: `value={{filter || 'all'}}` not empty string
- - HTML input elements require string values, so convert null → '' for display, '' → null for storage
-- State initialization should match API return types exactly
-
-# TypeScript Best Practices:
-- Always provide explicit types for all callbacks:
- - useState setters: `setData((prev: DataType) => ...)`
- - Event handlers: `onChange={{(e: React.ChangeEvent) => ...}}`
- - Array methods: `items.map((item: ItemType) => ...)`
-- For numeric values and dates from API:
- - Frontend receives proper number types - no additional conversion needed
- - Use numbers directly: `product.price.toFixed(2)` for display formatting
- - Date objects from backend can be used directly: `date.toLocaleDateString()`
-- NEVER use mock data or hardcoded values - always fetch real data from the API
-
-# React Hook Dependencies:
-- Follow React Hook rules strictly:
- - Include all dependencies in useEffect/useCallback/useMemo arrays
- - Wrap functions used in useEffect with useCallback if they use state/props
- - Use empty dependency array `[]` only for mount-only effects
- - Example pattern:
- ```typescript
- const loadData = useCallback(async () => {{
- // data loading logic
- }}, [dependency1, dependency2]);
-
- useEffect(() => {{
- loadData();
- }}, [loadData]);
- ```
-
-Working with backend follow these rules:
-
-Example Handler:
-{BASE_HANDLER_IMPLEMENTATION}
-
-Example Test:
-{BASE_HANDLER_TEST}
-
-# Implementation Rules:
-{DATABASE_PATTERNS}
-
-## Testing Best Practices:
-- Create reliable test setup: Use `beforeEach(createDB)` and `afterEach(resetDB)`
-- Create prerequisite data first (users, categories) before dependent records
-- Use flexible error assertions: `expect().rejects.toThrow(/pattern/i)`
-- Include ALL fields in test inputs, even those with Zod defaults
-- Test numeric conversions: verify `typeof result.price === 'number'`
-- CRITICAL handler type signatures:
- ```typescript
- // Handler should expect the PARSED Zod type (with defaults applied)
- export const searchProducts = async (input: SearchProductsInput): Promise => {{
- // input.limit and input.offset are guaranteed to exist here
- // because Zod has already parsed and applied defaults
- }};
-
- // If you need a handler that accepts pre-parsed input,
- // create a separate input type without defaults
- ```
-
-# Common Pitfalls to Avoid:
-1. **Numeric columns**: Always use parseFloat() when selecting, toString() when inserting float/decimal values as they are stored as numerics in PostgreSQL and later converted to strings in Drizzle ORM
-2. **Query conditions**: Use and(...conditions) with spread operator, NOT and(conditions)
-3. **Joined results**: Access data via nested properties (result.table1.field, result.table2.field)
-4. **Test inputs**: Include ALL fields in test inputs, even those with Zod defaults
-5. **Type annotations**: Use SQL[] for condition arrays
-6. **Query order**: Always apply .where() before .limit(), .offset(), or .orderBy()
-7. **Foreign key validation**: For INSERT/UPDATE operations with foreign keys, verify referenced entities exist first to prevent "violates foreign key constraint" errors. Ensure tests cover the use case where foreign keys are used.
-
-# Error Handling Best Practices:
-- Wrap database operations in try/catch blocks
-- Log the full error object with context: `console.error('Operation failed:', error);`
-- Rethrow original errors to preserve stack traces: `throw error;`
-- Error handling does not need to be tested in unit tests
-- Do not use other handlers in implementation or tests - keep fully isolated
-- NEVER use mocks - always test against real database operations
-
-{TOOL_USAGE_RULES}
-
-Rules for changing files:
-- To apply local changes use SEARCH / REPLACE format.
-- To change the file completely use the WHOLE format.
-- When using SEARCH / REPLACE maintain precise indentation for both search and replace.
-- Each block starts with a complete file path followed by newline with content enclosed with pair of ```.
-- Each SEARCH / REPLACE block contains a single search and replace pair formatted with
-<<<<<<< SEARCH
-// code to find
-=======
-// code to replace it with
->>>>>>> REPLACE
-
-
-Example WHOLE format:
-
-server/src/index.ts
-```
-const t = initTRPC.create({{
- transformer: superjson,
-}});
-```
-
-Example SEARCH / REPLACE format:
-
-server/src/helpers/reset.ts
-```
-<<<<<<< SEARCH
-resetDB().then(() => console.log('DB reset successfully'));
-=======
-import {{ resetDB }} from '.';
-resetDB().then(() => console.log('DB reset successfully'));
->>>>>>> REPLACE
-```
-""".strip()
-
-
-EDIT_ACTOR_USER_PROMPT = """
-{{ project_context }}
-
-Use the tools to create or modify files as needed and install required packages.
-Given original user request:
-{{ user_prompt }}
-Implement solely the required changes according to the user feedback:
-{{ feedback }}
-""".strip()
+"""
\ No newline at end of file