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
1 change: 1 addition & 0 deletions .agents/skills/fusion-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ C# (`asp-core`): `main.cs`, `*.csproj` (`net10.0`), `[Route]` / `[HttpGet]`.
### What the starter demonstrates

- `FusionBaseTemplate` at `/` (Tera templates; **not** listed in Swagger).
- Welcome UI via built-in components: `fusion.badge`, `fusion.button`, `fusion.card`, `fusion.table` (styles: `{% include "fusion/components.css" %}`).
- `FusionBaseApi` at `api/[module]` with `version="v1"` → `/v1/api/product/…`.
- Convention verbs (`get` / `post` / …) plus one custom slot (`http_get` / `httpGet` / `[HttpGet]` with `[action]`).
- Opt-in middleware list in `main` (e.g. `request_id`, `cors`, `cache_headers`, `security_headers`, `framework_headers`). Framework does **not** auto-enable middleware; the scaffold opts in.
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,8 @@ Desktop.ini
## Local secrets / overrides (keep examples/*.json samples in repo)
fusion.local.json
*.local.json


.tmp
.nuget
.sentry-native
26 changes: 8 additions & 18 deletions crates/fusion-core/assets/templates/fusion/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{% block title %}{{ title | default(value="Fusion") }}{% endblock %}</title>
<style>
:root { --fusion-primary: #2563eb; --fusion-muted: #64748b; --fusion-border: #e2e8f0; }
body { font-family: system-ui, sans-serif; margin: 0; padding: 2rem; color: #0f172a; background: #f8fafc; }
.fusion-btn { display: inline-block; padding: 0.5rem 1rem; border-radius: 0.375rem; border: none; cursor: pointer; font-weight: 600; text-decoration: none; }
.fusion-btn--primary { background: var(--fusion-primary); color: #fff; }
.fusion-btn--secondary { background: #e2e8f0; color: #0f172a; }
.fusion-btn--danger { background: #dc2626; color: #fff; }
.fusion-link { color: var(--fusion-primary); text-decoration: none; }
.fusion-link:hover { text-decoration: underline; }
.fusion-card { background: #fff; border: 1px solid var(--fusion-border); border-radius: 0.5rem; padding: 1.25rem; margin: 1rem 0; }
.fusion-card__title { margin: 0 0 0.75rem; font-size: 1.125rem; }
.fusion-alert { padding: 0.75rem 1rem; border-radius: 0.375rem; margin: 1rem 0; }
.fusion-alert--info { background: #dbeafe; color: #1e40af; }
.fusion-alert--success { background: #dcfce7; color: #166534; }
.fusion-alert--warning { background: #fef9c3; color: #854d0e; }
.fusion-alert--error { background: #fee2e2; color: #991b1b; }
.fusion-badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 9999px; font-size: 0.75rem; font-weight: 600; }
.fusion-badge--default { background: #e2e8f0; }
.fusion-badge--primary { background: #dbeafe; color: #1e40af; }
body {
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 2rem;
color: #0f172a;
background: #f8fafc;
}
{% include "fusion/components.css" %}
</style>
{% block head %}{% endblock %}
</head>
Expand Down
214 changes: 214 additions & 0 deletions crates/fusion-core/assets/templates/fusion/components.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/* Fusion built-in UI component styles (welcome-page look). */

:root {
--fusion-primary: #0f172a;
--fusion-accent: #6366f1;
--fusion-muted: #64748b;
--fusion-border: #e2e8f0;
--fusion-surface: #ffffff;
--fusion-bg: #f8fafc;
}

.fusion-btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 17px;
border-radius: 8px;
border: none;
cursor: pointer;
text-decoration: none;
font-size: 14px;
font-weight: 600;
line-height: 1.2;
transition: 0.2s ease;
box-sizing: border-box;
}

.fusion-btn--primary {
background: var(--fusion-primary);
color: #fff;
}

.fusion-btn--primary:hover {
background: #1e293b;
transform: translateY(-1px);
}

.fusion-btn--secondary {
background: var(--fusion-surface);
color: #334155;
border: 1px solid var(--fusion-border);
}

.fusion-btn--secondary:hover {
background: var(--fusion-bg);
transform: translateY(-1px);
}

.fusion-btn--danger {
background: #dc2626;
color: #fff;
}

.fusion-btn--danger:hover {
background: #b91c1c;
transform: translateY(-1px);
}

.fusion-link {
color: var(--fusion-accent);
text-decoration: none;
font-weight: 600;
}

.fusion-link:hover {
text-decoration: underline;
}

.fusion-card {
margin-top: 42px;
padding: 24px;
text-align: left;
background: var(--fusion-surface);
border: 1px solid var(--fusion-border);
border-radius: 14px;
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.05);
}

.fusion-card__title {
font-size: 14px;
font-weight: 700;
color: #334155;
margin: 0 0 14px;
}

.fusion-card__body {
color: #334155;
}

.fusion-alert {
padding: 0.75rem 1rem;
border-radius: 0.5rem;
margin: 1rem 0;
font-size: 14px;
font-weight: 600;
}

.fusion-alert--info {
background: #dbeafe;
color: #1e40af;
}

.fusion-alert--success {
background: #dcfce7;
color: #166534;
}

.fusion-alert--warning {
background: #fef9c3;
color: #854d0e;
}

.fusion-alert--error {
background: #fee2e2;
color: #991b1b;
}

.fusion-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 7px 13px;
border-radius: 999px;
font-size: 13px;
font-weight: 600;
line-height: 1;
}

.fusion-badge--default {
background: #e2e8f0;
color: #334155;
}

.fusion-badge--primary {
background: #e0e7ff;
color: #3730a3;
}

.fusion-badge--success {
background: #ecfdf5;
color: #047857;
}

.fusion-badge--warning {
background: #fffbeb;
color: #b45309;
}

.fusion-badge--danger {
background: #fef2f2;
color: #b91c1c;
}

.fusion-badge__dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}

.fusion-badge--success .fusion-badge__dot {
background: #10b981;
}

.fusion-table-wrap {
width: 100%;
overflow-x: auto;
margin: 1.5rem 0;
border: 1px solid var(--fusion-border);
border-radius: 14px;
background: var(--fusion-surface);
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.05);
}

.fusion-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
text-align: left;
}

.fusion-table caption {
caption-side: top;
padding: 14px 16px 0;
font-size: 14px;
font-weight: 700;
color: #334155;
text-align: left;
}

.fusion-table th,
.fusion-table td {
padding: 12px 16px;
border-bottom: 1px solid var(--fusion-border);
color: #334155;
}

.fusion-table th {
font-size: 12px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--fusion-muted);
background: #f8fafc;
}

.fusion-table tbody tr:last-child td {
border-bottom: none;
}

.fusion-table tbody tr:hover td {
background: #f8fafc;
}
47 changes: 41 additions & 6 deletions crates/fusion-core/assets/templates/fusion/macros.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
{# Fusion built-in UI components (Tera 2). Use: {{<fusion.button label="Save" variant="primary" />}} #}
{# Fusion built-in UI components (Tera 2).
Use: {{<fusion.button label="Save" variant="primary" />}}
Docs: include styles via {% include "fusion/components.css" %} or extend fusion/base.html
#}

{% component fusion.button(label: string, href: string = "", variant: string = "primary", type: string = "button") %}
<button type="{{ type }}" class="fusion-btn fusion-btn--{{ variant }}"{% if href and type != "submit" %} onclick="window.location.href='{{ href }}'"{% endif %}>{{ label }}</button>
{%- if href -%}
<a href="{{ href }}" class="fusion-btn fusion-btn--{{ variant }}">{{ label }}</a>
{%- else -%}
<button type="{{ type }}" class="fusion-btn fusion-btn--{{ variant }}">{{ label }}</button>
{%- endif -%}
{% endcomponent fusion.button %}

{% component fusion.link(label: string, href: string = "#", variant: string = "default") %}
Expand All @@ -10,15 +17,43 @@

{% component fusion.card(title: string = "", content: string = "") %}
<div class="fusion-card">
{% if title %}<h3 class="fusion-card__title">{{ title }}</h3>{% endif %}
<div class="fusion-card__body">{{ content | safe }}</div>
{% if title %}<div class="fusion-card__title">{{ title }}</div>{% endif %}
<div class="fusion-card__body">{% if content %}{{ content | safe }}{% else %}{{ body | safe }}{% endif %}</div>
</div>
{% endcomponent fusion.card %}

{% component fusion.alert(message: string, variant: string = "info") %}
<div class="fusion-alert fusion-alert--{{ variant }}" role="alert">{{ message }}</div>
{% endcomponent fusion.alert %}

{% component fusion.badge(label: string, variant: string = "default") %}
<span class="fusion-badge fusion-badge--{{ variant }}">{{ label }}</span>
{# Status/pill badge. Set dot={true} for the success-style indicator used on the welcome page. #}
{% component fusion.badge(label: string, variant: string = "default", dot: bool = false) %}
<span class="fusion-badge fusion-badge--{{ variant }}{% if dot %} fusion-badge--with-dot{% endif %}">
{%- if dot -%}<span class="fusion-badge__dot" aria-hidden="true"></span>{%- endif -%}
{{ label }}
</span>
{% endcomponent fusion.badge %}

{# Data table. Pass headers/rows as arrays, and/or nest custom <tr> markup as the body. #}
{% component fusion.table(headers: array = [], rows: array = [], caption: string = "") %}
<div class="fusion-table-wrap">
<table class="fusion-table">
{% if caption %}<caption>{{ caption }}</caption>{% endif %}
{% if headers %}
<thead>
<tr>
{% for h in headers %}<th scope="col">{{ h }}</th>{% endfor %}
</tr>
</thead>
{% endif %}
<tbody>
{% for row in rows %}
<tr>
{% for cell in row %}<td>{{ cell }}</td>{% endfor %}
</tr>
{% endfor %}
{{ body | safe }}
</tbody>
</table>
</div>
{% endcomponent fusion.table %}
Loading
Loading