Merge branch 'animations'

This commit is contained in:
Bryce
2025-08-12 23:03:16 -07:00
6 changed files with 127 additions and 4 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
**/*.pyc **/*.pyc
tmp/**/* tmp/**/*
.serena/cache/**

View File

@@ -1,5 +1,9 @@
{ {
"mcpServers": { "mcpServers": {
"serena": {
"command": "uvx",
"args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server", "--context", "ide-assistant", "--project", "/home/notid/dev/email-organizer"]
},
"context7": { "context7": {
"command": "npx", "command": "npx",
"args": [ "args": [

View File

@@ -15,6 +15,7 @@ Here are special rules you must follow:
13. Plans go into docs/plans/*.md. These may not be kept in sync, as they are just for brainstorming. 13. Plans go into docs/plans/*.md. These may not be kept in sync, as they are just for brainstorming.
14. ****IMPORTANT**** Database migrations are automatically created via `flask db migrate -m 'message'`. **NEVER** create migrations by hand. You should never have to read the contents of migrations/ 14. ****IMPORTANT**** Database migrations are automatically created via `flask db migrate -m 'message'`. **NEVER** create migrations by hand. You should never have to read the contents of migrations/
15. In the technical documentation, code should be used sparingly. Also, when needed, focus on the APIs, and only use snippets. 15. In the technical documentation, code should be used sparingly. Also, when needed, focus on the APIs, and only use snippets.
16. If appropriate context has been given in the prompt for the task at hand, don't read the whole file.
# Conventions # Conventions

44
.roomodes Normal file
View File

@@ -0,0 +1,44 @@
customModes:
- slug: user-story-creator
name: 📝 User Story Creator
roleDefinition: |
You are an agile requirements specialist focused on creating clear, valuable user stories. Your expertise includes:
- Crafting well-structured user stories following the standard format
- Breaking down complex requirements into manageable stories
- Identifying acceptance criteria and edge cases
- Ensuring stories deliver business value
- Maintaining consistent story quality and granularity
whenToUse: |
Use this mode when you need to create user stories, break down requirements into manageable pieces, or define acceptance criteria for features. Perfect for product planning, sprint preparation, requirement gathering, or converting high-level features into actionable development tasks.
description: Create structured agile user stories
groups:
- read
- edit
- command
source: project
customInstructions: |
Expected User Story Format:
Title: [Brief descriptive title]
As a [specific user role/persona],
I want to [clear action/goal],
So that [tangible benefit/value].
Acceptance Criteria:
1. [Criterion 1]
2. [Criterion 2]
3. [Criterion 3]
Story Types to Consider:
- Functional Stories (user interactions and features)
- Non-functional Stories (performance, security, usability)
- Epic Breakdown Stories (smaller, manageable pieces)
- Technical Stories (architecture, infrastructure)
Edge Cases and Considerations:
- Error scenarios
- Permission levels
- Data validation
- Performance requirements
- Security implications

View File

@@ -11,10 +11,33 @@ login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in to access this page.' login_manager.login_message = 'Please log in to access this page.'
login_manager.login_message_category = 'warning' login_manager.login_message_category = 'warning'
from flask import Flask, request, make_response
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from config import config
from app.models import db, Base, User
from flask_migrate import Migrate
# Initialize Flask-Login
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.login_message = 'Please log in to access this page.'
login_manager.login_message_category = 'warning'
def create_app(config_name='default'): def create_app(config_name='default'):
app = Flask(__name__, static_folder='static', static_url_path='/static') app = Flask(__name__, static_folder='static', static_url_path='/static')
app.config.from_object(config[config_name]) app.config.from_object(config[config_name])
# Add middleware to simulate 500 errors
@app.before_request
def check_for_simulate_500():
# Check if the X-Simulate-500 header is present
if request.headers.get('X-Simulate-500'):
response = make_response({'error': 'Simulated server error'}, 500)
response.headers['Content-Type'] = 'application/json'
return response
# Initialize extensions # Initialize extensions
db.init_app(app) db.init_app(app)
migrate = Migrate(app, db) migrate = Migrate(app, db)

View File

@@ -13,6 +13,37 @@
<script src="https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" defer></script> <script src="https://cdn.jsdelivr.net/npm/@ryangjchandler/alpine-tooltip@1.x.x/dist/cdn.min.js" defer></script>
<link rel="stylesheet" href="https://unpkg.com/tippy.js@6/dist/tippy.css" /> <link rel="stylesheet" href="https://unpkg.com/tippy.js@6/dist/tippy.css" />
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script>
document.addEventListener('alpine:init', () => {
Alpine.store('errorToast', {
show: false,
message: '',
hide() {
this.show = false;
},
handleError(detail) {
if (detail.xhr.status >= 500 && detail.xhr.status < 600) {
// Extract error message from response
let errorMessage = 'Server Error';
try {
const responseJson = JSON.parse(detail.xhr.response);
if (responseJson.error) {
errorMessage = responseJson.error;
}
} catch (e) {
// If not JSON, use the raw response
errorMessage = detail.xhr.response || 'Server Error';
}
// Set error message and show toast using Alpine store
this.message = errorMessage;
this.show = true;
}
}
});
});
</script>
<style> <style>
@keyframes shake { @keyframes shake {
0%, 100% { transform: translateX(0); } 0%, 100% { transform: translateX(0); }
@@ -48,14 +79,33 @@
</style> </style>
{% block head %}{% endblock %} {% block head %}{% endblock %}
</head> </head>
<body class="min-h-screen flex flex-col" x-data="{}" x-on:open-modal.window="$refs.modal.showModal()" <body
x-on:close-modal.window="$refs.modal.close()" x-on:htmx:response-error.camel="$store.errorToast.handleError($event.detail)"
hx-ext="loading-states" class="min-h-screen flex flex-col" x-data="{}" x-on:open-modal.window="$refs.modal.showModal()"
data-loading-delay="200"> x-on:close-modal.window="$refs.modal.close()" hx-ext="loading-states" data-loading-delay="200">
{% block header %}{% endblock %} {% block header %}{% endblock %}
{% block content %}{% endblock %} {% block content %}{% endblock %}
{% block modal %}{% endblock %} {% block modal %}{% endblock %}
<!-- Toast for 5xx errors -->
<div id="error-toast" class="toast toast-top toast-end w-full z-100" x-show="$store.errorToast.show" x-transition.duration.200ms>
<div class="alert alert-warning backdrop-blur-md bg-error/30 border border-error/20 relative">
<span id="error-message" x-text="$store.errorToast.message"></span>
<button class="btn btn-sm btn-ghost absolute top-2 right-2" @click="$store.errorToast.hide()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<script>
// Check for global variable to set X-Simulate-500 header
document.addEventListener('htmx:configRequest', function (evt) {
if (typeof window.simulate500 === 'boolean' && window.simulate500) {
evt.detail.headers['X-Simulate-500'] = 'true';
}
});
</script>
</body> </body>
</html> </html>