Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,27 @@ python seed_shops.py # ShopRules only, no inventory data (open-source safe)

# Docker
docker compose up --build

# Rebuild Tailwind CSS after editing a template or app/static/css/input.css
# (compiled output app/static/css/tailwind.css is committed, no CDN/Node dependency at runtime)
tailwindcss -i ./app/static/css/input.css -o ./app/static/css/tailwind.css --minify
tailwindcss -i ./app/static/css/input.css -o ./app/static/css/tailwind.css --watch # during active CSS work
```

No test suite, no linter config exists.
No test suite, no linter config exists (Pylint config in `.pylintrc`, run manually: `pylint app`).

## Architecture

**Stack:** Quart 0.20 (async Flask), SQLAlchemy 2.x async, asyncmy (MariaDB), Alembic, Jinja2, Tailwind CSS (CDN), APScheduler, httpx, selectolax, cloudscraper, Playwright.
**Stack:** Quart 0.20 (async Flask), SQLAlchemy 2.x async, asyncmy (MariaDB), Alembic, Jinja2, Tailwind CSS v4 (locally built, committed output, no CDN), APScheduler, httpx, selectolax, cloudscraper, Playwright.

**App factory:** `app/__init__.py` → `create_app()`. Registers blueprints, CSRF middleware, scheduler, error handlers (403/404/500), and a context processor that injects `current_user`, `csrf_token`, `nav_alert_count`, `is_admin` into every template.

**DB session:** Always use `async with get_db() as session:`. The context manager auto-commits on clean exit, rolls back on exception.

**Static files:** `app/static/` — served at `/static/`. Logo at `app/static/img/logo.png`, referenced via `url_for('static', filename='img/logo.png')`.

**CSS/Tailwind:** No CDN, no Node.js at runtime. `app/static/css/input.css` is the Tailwind v4 source (`@theme` block holds the custom `gray` palette + font, `@layer components` holds `.card`/`.btn-*`/`.badge-*`/`.nav-link-*` etc. — v4 forbids `@apply`-ing one custom component class from another, so related classes share a grouped selector instead). `app/static/css/tailwind.css` is the committed, built output referenced via `<link>` in every full-page template (`base.html`, `auth/login.html`, `auth/setup.html`, `inventory/spool_label.html`). Rebuild with the standalone Tailwind CLI (no npm install needed): `curl -sL -o tailwindcss https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 && chmod +x tailwindcss`, then the commands above. Commit the rebuilt `tailwind.css` alongside any template/class change.

Comment thread
Erik05Master marked this conversation as resolved.
**Blueprints / routes:**

| Blueprint | Prefix | File |
Expand Down
90 changes: 90 additions & 0 deletions app/static/css/input.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
@import "tailwindcss";

@theme {
--font-sans: -apple-system, BlinkMacSystemFont, system-ui, sans-serif;

--color-gray-50: #fafafa;
--color-gray-100: #f4f4f5;
--color-gray-200: #e4e4e7;
--color-gray-300: #d4d4d8;
--color-gray-400: #a1a1aa;
--color-gray-500: #71717a;
--color-gray-600: #52525b;
--color-gray-700: #3f3f46;
--color-gray-750: #323238;
--color-gray-800: #27272a;
--color-gray-850: #1e1e22;
--color-gray-900: #141417;
--color-gray-950: #09090b;
}

@layer base {
::selection { background-color: #6366f1; color: #fff; }
* { -webkit-font-smoothing: antialiased; }
}

@layer components {
/* Cards — chained via shared selector since v4 @apply can't reference another custom class */
.card, .card-hover, .stat-card {
@apply bg-gray-900 border border-gray-750 rounded-xl;
}
.card-hover {
@apply hover:border-gray-700 transition-colors duration-150;
}
.stat-card {
@apply p-5;
}
/* Typography */
.section-label {
@apply text-[10.5px] font-semibold text-gray-500 uppercase tracking-[0.08em];
}
.page-title {
@apply text-2xl font-bold text-white tracking-tight;
}
.page-subtitle {
@apply text-sm text-gray-500 mt-1;
}
/* Inputs */
.input-base {
@apply w-full bg-gray-850 border border-gray-750 rounded-lg px-3 py-2 text-sm text-white
placeholder-gray-600 focus:outline-none focus:border-indigo-500/60
focus:ring-2 focus:ring-indigo-500/15 transition-colors;
}
/* Buttons */
.btn-primary {
@apply inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-500
text-white text-sm font-medium rounded-lg transition-colors;
}
.btn-ghost {
@apply inline-flex items-center gap-2 px-4 py-2 bg-gray-800 hover:bg-gray-750
text-gray-300 hover:text-white text-sm font-medium rounded-lg transition-colors;
}
.btn-sm {
@apply px-3 py-1.5 text-xs rounded-lg;
}
.btn-icon {
@apply inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg
bg-indigo-500/10 text-indigo-400 hover:bg-indigo-500/20 hover:text-indigo-300
border border-indigo-500/20 transition-all;
}
/* Badges — shared base via grouped selector */
.badge, .badge-gray, .badge-indigo, .badge-amber, .badge-green, .badge-red, .badge-blue {
@apply inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium;
}
.badge-gray { @apply bg-gray-800 text-gray-400; }
.badge-indigo { @apply bg-indigo-500/15 text-indigo-400 ring-1 ring-inset ring-indigo-500/20; }
.badge-amber { @apply bg-amber-500/15 text-amber-400 ring-1 ring-inset ring-amber-500/20; }
.badge-green { @apply bg-emerald-500/15 text-emerald-400 ring-1 ring-inset ring-emerald-500/20; }
.badge-red { @apply bg-red-500/15 text-red-400 ring-1 ring-inset ring-red-500/20; }
.badge-blue { @apply bg-blue-500/15 text-blue-400 ring-1 ring-inset ring-blue-500/20; }
/* Nav — shared base via grouped selector */
.nav-link, .nav-link-active, .nav-link-inactive {
@apply flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors;
}
.nav-link-active {
@apply bg-white/[0.07] text-white;
}
.nav-link-inactive {
@apply text-gray-400 hover:text-gray-200 hover:bg-white/[0.04];
}
}
2 changes: 2 additions & 0 deletions app/static/css/tailwind.css

Large diffs are not rendered by default.

21 changes: 1 addition & 20 deletions app/templates/auth/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ t('auth.login.page_title') }} – {{ t('app.title') }}</title>

<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { sans: ['-apple-system', 'BlinkMacSystemFont', 'system-ui', 'sans-serif'] },
colors: {
gray: {
750: '#323238', 800: '#27272a', 850: '#1e1e22',
900: '#141417', 950: '#09090b',
}
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer base { ::selection { background-color: #6366f1; color: #fff; } * { -webkit-font-smoothing: antialiased; } }
</style>
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.css') }}">
</head>
<body class="h-full bg-gray-950 text-gray-100 font-sans flex items-center justify-center px-4">

Expand Down
21 changes: 1 addition & 20 deletions app/templates/auth/setup.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ t('auth.setup.page_title') }} – {{ t('app.title') }}</title>

<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { sans: ['-apple-system', 'BlinkMacSystemFont', 'system-ui', 'sans-serif'] },
colors: {
gray: {
750: '#323238', 800: '#27272a', 850: '#1e1e22',
900: '#141417', 950: '#09090b',
}
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer base { ::selection { background-color: #6366f1; color: #fff; } * { -webkit-font-smoothing: antialiased; } }
</style>
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.css') }}">
</head>
<body class="h-full bg-gray-950 text-gray-100 font-sans flex items-center justify-center px-4">

Expand Down
100 changes: 1 addition & 99 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,105 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ t('app.title') }}{% endblock %}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { sans: ['-apple-system', 'BlinkMacSystemFont', 'system-ui', 'sans-serif'] },
colors: {
gray: {
50: '#fafafa',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
700: '#3f3f46',
750: '#323238',
800: '#27272a',
850: '#1e1e22',
900: '#141417',
950: '#09090b',
}
}
}
}
}
</script>
<style type="text/tailwindcss">
@layer base {
::selection { background-color: #6366f1; color: #fff; }
* { -webkit-font-smoothing: antialiased; }
}
@layer components {
/* Cards */
.card {
@apply bg-gray-900 border border-gray-750 rounded-xl;
}
.card-hover {
@apply card hover:border-gray-700 transition-colors duration-150;
}
/* Typography */
.section-label {
@apply text-[10.5px] font-semibold text-gray-500 uppercase tracking-[0.08em];
}
.page-title {
@apply text-2xl font-bold text-white tracking-tight;
}
.page-subtitle {
@apply text-sm text-gray-500 mt-1;
}
/* Inputs */
.input-base {
@apply w-full bg-gray-850 border border-gray-750 rounded-lg px-3 py-2 text-sm text-white
placeholder-gray-600 focus:outline-none focus:border-indigo-500/60
focus:ring-2 focus:ring-indigo-500/15 transition-colors;
}
/* Buttons */
.btn-primary {
@apply inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 hover:bg-indigo-500
text-white text-sm font-medium rounded-lg transition-colors;
}
.btn-ghost {
@apply inline-flex items-center gap-2 px-4 py-2 bg-gray-800 hover:bg-gray-750
text-gray-300 hover:text-white text-sm font-medium rounded-lg transition-colors;
}
.btn-sm {
@apply px-3 py-1.5 text-xs rounded-lg;
}
.btn-icon {
@apply inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-lg
bg-indigo-500/10 text-indigo-400 hover:bg-indigo-500/20 hover:text-indigo-300
border border-indigo-500/20 transition-all;
}
/* Badges */
.badge {
@apply inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium;
}
.badge-gray { @apply badge bg-gray-800 text-gray-400; }
.badge-indigo { @apply badge bg-indigo-500/15 text-indigo-400 ring-1 ring-inset ring-indigo-500/20; }
.badge-amber { @apply badge bg-amber-500/15 text-amber-400 ring-1 ring-inset ring-amber-500/20; }
.badge-green { @apply badge bg-emerald-500/15 text-emerald-400 ring-1 ring-inset ring-emerald-500/20; }
.badge-red { @apply badge bg-red-500/15 text-red-400 ring-1 ring-inset ring-red-500/20; }
.badge-blue { @apply badge bg-blue-500/15 text-blue-400 ring-1 ring-inset ring-blue-500/20; }
/* Nav */
.nav-link {
@apply flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium transition-colors;
}
.nav-link-active {
@apply nav-link bg-white/[0.07] text-white;
}
.nav-link-inactive {
@apply nav-link text-gray-400 hover:text-gray-200 hover:bg-white/[0.04];
}
/* Stat card variant */
.stat-card {
@apply card p-5;
}
}
</style>
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.css') }}">
</head>
<body class="h-full text-gray-100 bg-gray-950 font-sans antialiased">

Expand Down
20 changes: 2 additions & 18 deletions app/templates/inventory/spool_label.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,10 @@
<!DOCTYPE html>
<html lang="en" class="h-full">
<html lang="{{ current_locale }}" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ t('inventory.spool_label.title', code=spool.spool_code) }}</title>

<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: { sans: ['-apple-system', 'BlinkMacSystemFont', 'system-ui', 'sans-serif'] },
colors: {
gray: {
750: '#323238', 800: '#27272a', 850: '#1e1e22',
900: '#141417', 950: '#09090b',
}
}
}
}
}
</script>
<link rel="stylesheet" href="{{ url_for('static', filename='css/tailwind.css') }}">
<style>
@media print {
.no-print { display: none !important; }
Expand Down
Loading