feat(web/ui): merge ui polish round into dev
This commit is contained in:
@@ -173,9 +173,12 @@ export const ConfigProfileEditorPanel = ({
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-section">
|
||||
<div className="row justify-between items-center mb-3">
|
||||
<h4 className="m-0">Includes</h4>
|
||||
<section className="config-section">
|
||||
<div className="config-section-header">
|
||||
<div>
|
||||
<h4 className="config-section-title">Includes</h4>
|
||||
<p className="config-section-subtitle">Compose configurations from other profiles</p>
|
||||
</div>
|
||||
<span className="muted text-sm">{includedProfileIds.length} included</span>
|
||||
</div>
|
||||
{includedProfileIds.length === 0 ? (
|
||||
@@ -216,10 +219,10 @@ export const ConfigProfileEditorPanel = ({
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="form-section">
|
||||
<h4 className="m-0 mb-3">Environment Variables</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Environment Variables</h4>
|
||||
{Object.entries(formData.env_vars || {}).map(([key, value], idx) => (
|
||||
<div key={idx} className="form-row row-sm mb-2">
|
||||
<input type="text" value={key} onChange={(e) => onUpdateEnvVar(key, e.target.value, value)} placeholder="VAR_NAME" className="form-input" />
|
||||
@@ -228,16 +231,20 @@ export const ConfigProfileEditorPanel = ({
|
||||
</div>
|
||||
))}
|
||||
<button type="button" className="secondary-button" onClick={onAddEnvVar}><Icon name="add" size="sm" /> Add Variable</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="form-section">
|
||||
<h4 className="m-0 mb-3">Runtime Hints</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Runtime Hints</h4>
|
||||
<textarea value={JSON.stringify(formData.runtime_hints || {}, null, 2)} onChange={(e) => { try { const parsed = JSON.parse(e.target.value); onFormChange("runtime_hints", parsed); } catch { /* ignore */ } }} placeholder='{"start_command": "npm start"}' rows={4} className="form-input font-mono text-sm" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="form-section">
|
||||
<h4 className="m-0 mb-2">Files</h4>
|
||||
<p className="muted text-sm m-0 mb-3">Relative paths written to the instance directory. Use Mounts below for absolute container paths.</p>
|
||||
<section className="config-section">
|
||||
<div className="config-section-header">
|
||||
<div>
|
||||
<h4 className="config-section-title">Files</h4>
|
||||
<p className="config-section-subtitle">Relative paths written to the instance directory</p>
|
||||
</div>
|
||||
</div>
|
||||
{Object.entries(formData.files || {}).map(([path, content], idx) => (
|
||||
<div key={idx} className="card-sm mb-2">
|
||||
<div className="form-row row-sm mb-2">
|
||||
@@ -248,11 +255,15 @@ export const ConfigProfileEditorPanel = ({
|
||||
</div>
|
||||
))}
|
||||
<button type="button" className="secondary-button" onClick={onAddFile}><Icon name="add" size="sm" /> Add File</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="form-section">
|
||||
<h4 className="m-0 mb-2">Mounts</h4>
|
||||
<p className="muted text-sm m-0 mb-3">Bind directories into the container at absolute paths. Files are relative to the mount target.</p>
|
||||
<section className="config-section">
|
||||
<div className="config-section-header">
|
||||
<div>
|
||||
<h4 className="config-section-title">Mounts</h4>
|
||||
<p className="config-section-subtitle">Bind directories into the container at absolute paths</p>
|
||||
</div>
|
||||
</div>
|
||||
{(formData.mounts || []).map((mount, index) => (
|
||||
<div key={index} className="card-sm mb-3">
|
||||
<div className="form-row row-sm mb-3">
|
||||
@@ -276,11 +287,12 @@ export const ConfigProfileEditorPanel = ({
|
||||
</div>
|
||||
))}
|
||||
<button type="button" className="secondary-button" onClick={onAddMount}><Icon name="add" size="sm" /> Add Mount</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="form-section">
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Git Mounts</h4>
|
||||
<GitMountEditor mounts={formData.git_mounts || []} onChange={(git_mounts) => onFormChange("git_mounts", git_mounts)} />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="dialog-actions sticky-footer">
|
||||
<button type="submit" disabled={saveStatus === "saving"} className="btn btn-primary">
|
||||
|
||||
@@ -119,89 +119,99 @@ export const ProjectCard = ({
|
||||
|
||||
{expanded && (
|
||||
<div className="project-detail">
|
||||
{(project.repositories || []).length === 0 ? (
|
||||
<p className="muted">No repositories yet.</p>
|
||||
) : (
|
||||
<div className="repo-list">
|
||||
{(project.repositories || []).map((repo) => (
|
||||
<div key={repo.id} className="card card-md repo-block">
|
||||
<div className="repo-header">
|
||||
<h4>{repo.name}</h4>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => onCreateWorkspace(repo.id)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="add" size="sm" /> New Workspace
|
||||
</button>
|
||||
</div>
|
||||
{showCreateForm === repo.id && (
|
||||
<WorkspaceCreateForm
|
||||
defaultProjectId={project.id}
|
||||
defaultRepoId={repo.id}
|
||||
onSubmit={onCreated}
|
||||
onCancel={onCancelCreate}
|
||||
/>
|
||||
)}
|
||||
{repo.workspaces.length === 0 ? (
|
||||
<p className="muted">No workspaces.</p>
|
||||
) : (
|
||||
<div className="workspace-grid">
|
||||
{repo.workspaces.map((ws) => (
|
||||
<div
|
||||
key={ws.id}
|
||||
className={`workspace-chip ${ws.status}`}
|
||||
>
|
||||
<a href={`/workspaces/${ws.id}`}>{ws.name}</a>
|
||||
<span className="ws-branch">
|
||||
<Icon name="branch" size="sm" /> {ws.branch}
|
||||
</span>
|
||||
{ws.instance_count > 0 && (
|
||||
<span className="ws-instances">
|
||||
{ws.instance_count} tool
|
||||
{ws.instance_count > 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
<div className="ws-actions">
|
||||
<button
|
||||
type="button"
|
||||
disabled={workspaceLoading === ws.id}
|
||||
onClick={() =>
|
||||
onWorkspaceAction(repo.id, ws, "sync")
|
||||
}
|
||||
>
|
||||
<Icon name="refresh" size="sm" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="danger-text"
|
||||
disabled={workspaceLoading === ws.id}
|
||||
onClick={() =>
|
||||
onWorkspaceAction(repo.id, ws, "delete")
|
||||
}
|
||||
>
|
||||
<Icon name="delete" size="sm" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<section className="project-repos">
|
||||
<div className="project-repos-header">
|
||||
<div>
|
||||
<h4 className="config-section-title">Repositories</h4>
|
||||
<p className="config-section-subtitle">
|
||||
{project.repositories?.length ?? 0} repo
|
||||
{(project.repositories?.length ?? 0) !== 1 ? "s" : ""}
|
||||
</p>
|
||||
</div>
|
||||
{onAddRepository && (
|
||||
<button
|
||||
className="btn btn-sm btn-secondary"
|
||||
onClick={onAddRepository}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="add" size="sm" /> Add Repository
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(project.repositories || []).length === 0 ? (
|
||||
<p className="muted">No repositories yet.</p>
|
||||
) : (
|
||||
<div className="repo-list">
|
||||
{(project.repositories || []).map((repo) => (
|
||||
<div key={repo.id} className="card card-md repo-block">
|
||||
<div className="repo-header">
|
||||
<h4>{repo.name}</h4>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => onCreateWorkspace(repo.id)}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="add" size="sm" /> New Workspace
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{onAddRepository && (
|
||||
<div className="project-add-repo">
|
||||
<button
|
||||
className="primary-button"
|
||||
onClick={onAddRepository}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="add" size="sm" /> Add Repository
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{showCreateForm === repo.id && (
|
||||
<WorkspaceCreateForm
|
||||
defaultProjectId={project.id}
|
||||
defaultRepoId={repo.id}
|
||||
onSubmit={onCreated}
|
||||
onCancel={onCancelCreate}
|
||||
/>
|
||||
)}
|
||||
{repo.workspaces.length === 0 ? (
|
||||
<p className="muted">No workspaces.</p>
|
||||
) : (
|
||||
<div className="workspace-grid">
|
||||
{repo.workspaces.map((ws) => (
|
||||
<div
|
||||
key={ws.id}
|
||||
className={`workspace-chip ${ws.status}`}
|
||||
>
|
||||
<a href={`/workspaces/${ws.id}`}>{ws.name}</a>
|
||||
<span className="ws-branch">
|
||||
<Icon name="branch" size="sm" /> {ws.branch}
|
||||
</span>
|
||||
{ws.instance_count > 0 && (
|
||||
<span className="ws-instances">
|
||||
{ws.instance_count} tool
|
||||
{ws.instance_count > 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
<div className="ws-actions">
|
||||
<button
|
||||
type="button"
|
||||
disabled={workspaceLoading === ws.id}
|
||||
onClick={() =>
|
||||
onWorkspaceAction(repo.id, ws, "sync")
|
||||
}
|
||||
>
|
||||
<Icon name="refresh" size="sm" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="danger-text"
|
||||
disabled={workspaceLoading === ws.id}
|
||||
onClick={() =>
|
||||
onWorkspaceAction(repo.id, ws, "delete")
|
||||
}
|
||||
>
|
||||
<Icon name="delete" size="sm" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
|
||||
@@ -286,8 +286,8 @@ export const ManifestEditor = ({
|
||||
return (
|
||||
<div className="stack stack-lg">
|
||||
{/* Base Image */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Base Image</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Base Image</h4>
|
||||
<div className="form-row row-md">
|
||||
<div className="form-group">
|
||||
<label>Base Definition</label>
|
||||
@@ -322,11 +322,11 @@ export const ManifestEditor = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Packages */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Packages</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Packages</h4>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Node.js Version</label>
|
||||
@@ -435,11 +435,11 @@ export const ManifestEditor = ({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Runtime User */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Runtime User</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Runtime User</h4>
|
||||
<div className="form-row row-md">
|
||||
<div className="form-group">
|
||||
<label>User Name</label>
|
||||
@@ -469,11 +469,11 @@ export const ManifestEditor = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Environment */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Environment Variables</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Environment Variables</h4>
|
||||
<div className="stack stack-sm">
|
||||
{envVars.map((ev, idx) => (
|
||||
<div key={idx} className="form-row row-sm">
|
||||
@@ -509,11 +509,11 @@ export const ManifestEditor = ({
|
||||
<Icon name="add" size="sm" /> Add Env Var
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Build Scripts */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Build Scripts (run during docker build)</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Build Scripts (run during docker build)</h4>
|
||||
<div className="stack stack-sm">
|
||||
{buildScripts.map((script, idx) => (
|
||||
<div
|
||||
@@ -545,11 +545,11 @@ export const ManifestEditor = ({
|
||||
<Icon name="add" size="sm" /> Add Build Script
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Startup Scripts */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">
|
||||
Startup Scripts (run when container starts)
|
||||
</h4>
|
||||
<div className="stack stack-sm">
|
||||
@@ -583,11 +583,11 @@ export const ManifestEditor = ({
|
||||
<Icon name="add" size="sm" /> Add Startup Script
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Mounts */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Mount Schema</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Mount Schema</h4>
|
||||
<div className="stack stack-md">
|
||||
{mounts.map((mount, idx) => (
|
||||
<div
|
||||
@@ -701,11 +701,11 @@ export const ManifestEditor = ({
|
||||
<Icon name="add" size="sm" /> Add Mount
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Runtime */}
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Runtime</h4>
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Runtime</h4>
|
||||
<div className="form-row row-md">
|
||||
<div className="form-group">
|
||||
<label>Command</label>
|
||||
@@ -746,7 +746,7 @@ export const ManifestEditor = ({
|
||||
tty
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Preview */}
|
||||
<div className="card-md stack stack-md">
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
justify-content: space-between;
|
||||
gap: var(--space-4);
|
||||
align-items: flex-start;
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.home-hero-actions {
|
||||
@@ -123,11 +124,19 @@
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
}
|
||||
|
||||
.home-summary-card {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.home-project-grid,
|
||||
.home-session-grid {
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
}
|
||||
|
||||
.home-section {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.home-section h2,
|
||||
.settings-header h1,
|
||||
.settings-panel h2 {
|
||||
@@ -139,6 +148,14 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.home-hero,
|
||||
.home-summary-card,
|
||||
.home-section {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
}
|
||||
|
||||
.row-tight {
|
||||
gap: var(--space-2);
|
||||
}
|
||||
@@ -576,6 +593,49 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Shared config section — used by Tool Workshop and Config Profiles */
|
||||
.config-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.config-section-title {
|
||||
margin: 0 0 var(--space-1);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
line-height: var(--line-height-tight);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.config-section-subtitle {
|
||||
margin: calc(var(--space-1) * -1) 0 var(--space-2);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--muted);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
.config-section > .config-section-title + .config-section-subtitle {
|
||||
margin-top: calc(var(--space-2) * -1);
|
||||
}
|
||||
|
||||
.config-section-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.config-section-header .config-section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Legacy alias for existing consumers */
|
||||
.form-section {
|
||||
padding: var(--space-4);
|
||||
background: var(--panel);
|
||||
|
||||
@@ -45,24 +45,40 @@
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.project-repos {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.project-repos-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.project-repos-header .config-section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.repo-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.repo-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
background: var(--bg);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.repo-block,
|
||||
.repo-block.card-md {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.repo-header {
|
||||
@@ -139,11 +155,14 @@
|
||||
}
|
||||
|
||||
.project-add-repo {
|
||||
margin-top: var(--space-4);
|
||||
padding-top: var(--space-4);
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.project-repos-header {
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inline radio buttons for repository creation mode */
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# SDD Proposal: UI Polish Round — Config Sections, Home Paddings, Project Repos
|
||||
|
||||
## Status
|
||||
**Phase:** proposal
|
||||
**Date:** 2026-06-16
|
||||
**Owner:** el Gentleman
|
||||
|
||||
---
|
||||
|
||||
## User Story
|
||||
|
||||
As a Headquarter user, I want the Tool Workshop and Config Profiles editors to share a consistent section pattern, the Home page to have readable card paddings, and the Projects page to clearly show which repositories belong to a project so that the UI feels coherent and scannable.
|
||||
|
||||
---
|
||||
|
||||
## Problems
|
||||
|
||||
1. **Config sections are inconsistent**
|
||||
- Config Profiles uses `.form-section`.
|
||||
- Tool Workshop's `manifest-editor.tsx` uses `.card-md.stack.stack-md` with bare `<h4>` headings.
|
||||
- Both serve the same purpose: a grouped configuration block with a title.
|
||||
|
||||
2. **Home page cards lost padding**
|
||||
- The `.home-summary-card` has no padding.
|
||||
- `.home-hero` and `.home-section` may have lost explicit padding during the card consolidation pass.
|
||||
- The result is cards that touch their borders and feel cramped.
|
||||
|
||||
3. **Project repositories look misaligned**
|
||||
- In the expanded project card, the repository list is rendered but visually floats on the right/indented.
|
||||
- There is no clear "Repositories" header.
|
||||
- The "Add Repository" button is detached from the repo list; it is unclear whether it adds a repo or performs a project-level action.
|
||||
|
||||
---
|
||||
|
||||
## Goals
|
||||
|
||||
1. Introduce a single shared `.config-section` component class and use it in both Config Profiles and Tool Workshop.
|
||||
2. Restore comfortable, token-based padding on Home hero, summary cards, and section cards.
|
||||
3. Left-align repositories under the project header, add a clear "Repositories" section header, and visually attach the "Add Repository" button to the repo list.
|
||||
|
||||
---
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No changes to backend APIs or data models.
|
||||
- No redesign of the Home page layout or information architecture.
|
||||
- No changes to mobile project list behavior.
|
||||
- No new features (e.g., no new repo actions).
|
||||
|
||||
---
|
||||
|
||||
## High-Level Approach
|
||||
|
||||
1. **Shared config section**
|
||||
- Add `.config-section` and `.config-section-title` to `global.css`.
|
||||
- Refactor `manifest-editor.tsx` to use `.config-section` instead of `.card-md.stack.stack-md` + `<h4>`.
|
||||
- Refactor `ConfigProfileEditorPanel.tsx` to use `.config-section` instead of `.form-section` for its grouped blocks.
|
||||
|
||||
2. **Home padding fix**
|
||||
- Add explicit padding to `.home-hero`, `.home-summary-card`, and `.home-section`.
|
||||
- Use `--space-*` tokens.
|
||||
|
||||
3. **Project repo layout**
|
||||
- In `ProjectCard.tsx`, wrap repositories in a `<section className="project-repos">`.
|
||||
- Add a header row with "Repositories" label and repo count.
|
||||
- Move the "Add Repository" button into the repo section footer, directly under the list.
|
||||
- Left-align `.repo-list` so blocks fill from the left edge of the project detail area.
|
||||
- Update `styles/pages/projects.css` accordingly.
|
||||
|
||||
---
|
||||
|
||||
## Risks
|
||||
|
||||
| Risk | Severity | Mitigation |
|
||||
|------|----------|------------|
|
||||
| `.form-section` has other consumers outside config profiles | Low | Keep `.form-section` as alias; do not delete. |
|
||||
| `.card-md` consumers outside manifest editor | Low | `.config-section` will be equivalent; verify no visual change. |
|
||||
| Project repo layout changes affect mobile | Medium | Test both desktop and mobile expanded project card. |
|
||||
|
||||
---
|
||||
|
||||
## Effort Estimate
|
||||
|
||||
| Area | Files | Lines (est) |
|
||||
|---|---|---|
|
||||
| Shared config section | 3 | ~120 |
|
||||
| Home padding | 2 | ~40 |
|
||||
| Project repo layout | 2 | ~100 |
|
||||
| OpenSpec artifacts | 3 | ~80 |
|
||||
| **Total** | **10** | **~340** |
|
||||
|
||||
Review workload is within the 400-line budget.
|
||||
|
||||
---
|
||||
|
||||
## Next Recommended Phase
|
||||
|
||||
**Spec** — detail the exact CSS rules, component markup changes, and acceptance criteria.
|
||||
|
||||
Should I proceed to spec?
|
||||
@@ -0,0 +1,332 @@
|
||||
# OpenSpec Spec: UI Polish Round — Config Sections, Home Paddings, Project Repos
|
||||
|
||||
## Change
|
||||
`ui-polish-round-config-sections-home-projects`
|
||||
|
||||
## Parent Proposal
|
||||
`openspec/proposals/ui-polish-round-config-sections-home-projects.md`
|
||||
|
||||
## Status
|
||||
spec
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope
|
||||
|
||||
This spec covers three UI polish fixes:
|
||||
|
||||
1. **Unify config sections** across Tool Workshop and Config Profiles.
|
||||
2. **Restore paddings** on the Home page cards.
|
||||
3. **Improve project repository layout** in the expandable project card.
|
||||
|
||||
---
|
||||
|
||||
## 2. Shared Config Section
|
||||
|
||||
### 2.1 New CSS classes
|
||||
|
||||
Add to `apps/web/src/styles/global.css`:
|
||||
|
||||
```css
|
||||
.config-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
padding: var(--space-4);
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.config-section-title {
|
||||
margin: 0 0 var(--space-1);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: 600;
|
||||
line-height: var(--line-height-tight);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.config-section-subtitle {
|
||||
margin: calc(var(--space-1) * -1) 0 var(--space-2);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--muted);
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
.config-section > .config-section-title + .config-section-subtitle {
|
||||
margin-top: calc(var(--space-2) * -1);
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Tool Workshop migration
|
||||
|
||||
In `apps/web/src/components/features/tool/manifest-editor.tsx`:
|
||||
|
||||
- Replace every occurrence of:
|
||||
```tsx
|
||||
<div className="card-md stack stack-md">
|
||||
<h4 className="m-0">Title</h4>
|
||||
...
|
||||
</div>
|
||||
```
|
||||
with:
|
||||
```tsx
|
||||
<section className="config-section">
|
||||
<h4 className="config-section-title">Title</h4>
|
||||
...
|
||||
</section>
|
||||
```
|
||||
|
||||
- Keep `.card-md` on the preview block if it is not a config section.
|
||||
|
||||
### 2.3 Config Profiles migration
|
||||
|
||||
In `apps/web/src/components/features/config-profiles/ConfigProfileEditorPanel.tsx`:
|
||||
|
||||
- Replace every occurrence of:
|
||||
```tsx
|
||||
<div className="form-section">
|
||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "0.75rem" }}>
|
||||
<h4 style={{ margin: 0 }}>Title</h4>
|
||||
...
|
||||
</div>
|
||||
...
|
||||
</div>
|
||||
```
|
||||
with:
|
||||
```tsx
|
||||
<section className="config-section">
|
||||
<div className="config-section-header">
|
||||
<div>
|
||||
<h4 className="config-section-title">Title</h4>
|
||||
{subtitle && <p className="config-section-subtitle">{subtitle}</p>}
|
||||
</div>
|
||||
...actions...
|
||||
</div>
|
||||
...
|
||||
</section>
|
||||
```
|
||||
|
||||
- Add `.config-section-header` CSS:
|
||||
```css
|
||||
.config-section-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.config-section-header .config-section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 Backward compatibility
|
||||
|
||||
Keep `.form-section` defined as an alias so existing consumers outside Config Profiles are not broken.
|
||||
|
||||
---
|
||||
|
||||
## 3. Home Page Padding
|
||||
|
||||
### 3.1 Target classes
|
||||
|
||||
In `apps/web/src/styles/global.css`:
|
||||
|
||||
```css
|
||||
.home-hero {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.home-summary-card {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
|
||||
.home-section {
|
||||
padding: var(--space-4);
|
||||
}
|
||||
```
|
||||
|
||||
If `.home-hero` already has padding from a previous rule, override/ensure it uses `--space-4`.
|
||||
|
||||
### 3.2 Mobile adjustments
|
||||
|
||||
At `max-width: 767px`, reduce padding to `--space-3`:
|
||||
|
||||
```css
|
||||
@media (max-width: 767px) {
|
||||
.home-hero,
|
||||
.home-summary-card,
|
||||
.home-section {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Project Repository Layout
|
||||
|
||||
### 4.1 Component markup
|
||||
|
||||
In `apps/web/src/components/features/project/ProjectCard.tsx`:
|
||||
|
||||
Change the expanded detail block from:
|
||||
|
||||
```tsx
|
||||
{expanded && (
|
||||
<div className="project-detail">
|
||||
{(project.repositories || []).length === 0 ? (
|
||||
<p className="muted">No repositories yet.</p>
|
||||
) : (
|
||||
<div className="repo-list">...</div>
|
||||
)}
|
||||
{onAddRepository && (
|
||||
<div className="project-add-repo">...</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```tsx
|
||||
{expanded && (
|
||||
<div className="project-detail">
|
||||
<section className="project-repos">
|
||||
<div className="project-repos-header">
|
||||
<div>
|
||||
<h4 className="config-section-title">Repositories</h4>
|
||||
<p className="config-section-subtitle">
|
||||
{project.repositories?.length ?? 0} repo
|
||||
{(project.repositories?.length ?? 0) !== 1 ? "s" : ""}
|
||||
</p>
|
||||
</div>
|
||||
{onAddRepository && (
|
||||
<button
|
||||
className="btn btn-sm btn-secondary"
|
||||
onClick={onAddRepository}
|
||||
type="button"
|
||||
>
|
||||
<Icon name="add" size="sm" /> Add Repository
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(project.repositories || []).length === 0 ? (
|
||||
<p className="muted">No repositories yet.</p>
|
||||
) : (
|
||||
<div className="repo-list">
|
||||
{(project.repositories || []).map((repo) => (
|
||||
<div key={repo.id} className="card card-md repo-block">
|
||||
...
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
Remove the old `.project-add-repo` block.
|
||||
|
||||
### 4.2 CSS changes
|
||||
|
||||
In `apps/web/src/styles/pages/projects.css`:
|
||||
|
||||
```css
|
||||
.project-detail {
|
||||
margin-top: var(--space-4);
|
||||
padding-top: var(--space-4);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.project-repos {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.project-repos-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.project-repos-header .config-section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.repo-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.repo-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.repo-block,
|
||||
.repo-block.card-md {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
/* Remove old .project-add-repo rules */
|
||||
.project-add-repo {
|
||||
display: none;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 Mobile
|
||||
|
||||
At `max-width: 767px`, stack `.project-repos-header` vertically:
|
||||
|
||||
```css
|
||||
@media (max-width: 767px) {
|
||||
.project-repos-header {
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Acceptance Criteria
|
||||
|
||||
- [ ] `.config-section`, `.config-section-title`, `.config-section-subtitle`, `.config-section-header` exist in `global.css`.
|
||||
- [ ] `manifest-editor.tsx` uses `.config-section` for its grouped blocks.
|
||||
- [ ] `ConfigProfileEditorPanel.tsx` uses `.config-section` for its grouped blocks.
|
||||
- [ ] `.form-section` remains as a backward-compatible alias.
|
||||
- [ ] `.home-hero`, `.home-summary-card`, and `.home-section` have `--space-4` padding (desktop) and `--space-3` padding (mobile).
|
||||
- [ ] `ProjectCard.tsx` shows a "Repositories" header with repo count and an "Add Repository" button in the section header.
|
||||
- [ ] Repository blocks are left-aligned under the project header and fill the available width.
|
||||
- [ ] Old `.project-add-repo` standalone block is removed.
|
||||
- [ ] `npm run typecheck` passes.
|
||||
- [ ] `npm run lint` passes.
|
||||
|
||||
---
|
||||
|
||||
## 6. Verification Plan
|
||||
|
||||
1. Run `cd apps/web && npm run typecheck`.
|
||||
2. Run `cd apps/web && npm run lint`.
|
||||
3. Open Tool Workshop → create/edit a tool type → verify section styling matches Config Profiles.
|
||||
4. Open Config Profiles → verify section headers and subtitles render correctly.
|
||||
5. Open Home → verify hero, summary cards, and section cards have comfortable padding.
|
||||
6. Open Projects → expand a project → verify repositories are left-aligned, have a "Repositories" header with count, and the "Add Repository" button is in the header.
|
||||
7. Test project card on mobile viewport.
|
||||
|
||||
---
|
||||
|
||||
## 7. Next Phase
|
||||
|
||||
After approval, create tasks and delegate to `sdd-apply`.
|
||||
@@ -0,0 +1,76 @@
|
||||
# OpenSpec Tasks: UI Polish Round — Config Sections, Home Paddings, Project Repos
|
||||
|
||||
## Change
|
||||
`ui-polish-round-config-sections-home-projects`
|
||||
|
||||
## Parent Spec
|
||||
`openspec/specs/ui-polish-round-config-sections-home-projects.md`
|
||||
|
||||
## Status
|
||||
tasks
|
||||
|
||||
---
|
||||
|
||||
## Implementation Tasks
|
||||
|
||||
- [x] 1. Add shared config-section classes to `apps/web/src/styles/global.css`
|
||||
- `.config-section`
|
||||
- `.config-section-title`
|
||||
- `.config-section-subtitle`
|
||||
- `.config-section-header`
|
||||
- Keep `.form-section` as backward-compatible alias
|
||||
|
||||
- [x] 2. Refactor `apps/web/src/components/features/tool/manifest-editor.tsx`
|
||||
- Replace `.card-md.stack.stack-md` + `<h4 className="m-0">` blocks with `.config-section` + `.config-section-title`
|
||||
- Keep preview block as `.card-md` if it is not a config section
|
||||
|
||||
- [x] 3. Refactor `apps/web/src/components/features/config-profiles/ConfigProfileEditorPanel.tsx`
|
||||
- Replace `.form-section` grouped blocks with `.config-section`
|
||||
- Use `.config-section-header` for title + action rows
|
||||
- Add `.config-section-subtitle` where helpful
|
||||
|
||||
- [x] 4. Fix Home page paddings in `apps/web/src/styles/global.css`
|
||||
- `.home-hero`: `--space-4` padding
|
||||
- `.home-summary-card`: `--space-4` padding
|
||||
- `.home-section`: `--space-4` padding
|
||||
- Mobile (`max-width: 767px`): reduce to `--space-3`
|
||||
|
||||
- [x] 5. Improve project repository layout in `apps/web/src/components/features/project/ProjectCard.tsx`
|
||||
- Wrap repositories in `<section className="project-repos">`
|
||||
- Add `.project-repos-header` with "Repositories" title, repo count, and "Add Repository" button
|
||||
- Remove old standalone `.project-add-repo` block
|
||||
|
||||
- [x] 6. Update `apps/web/src/styles/pages/projects.css`
|
||||
- Left-align `.repo-list`
|
||||
- Add `.project-repos` and `.project-repos-header` styles
|
||||
- Remove or hide old `.project-add-repo` rules
|
||||
- Add mobile stacking for `.project-repos-header`
|
||||
|
||||
- [x] 7. Verification
|
||||
- Run `cd apps/web && npm run typecheck`
|
||||
- Run `cd apps/web && npm run lint`
|
||||
- Spot-check Tool Workshop, Config Profiles, Home, and Projects pages
|
||||
- Test project card on mobile viewport
|
||||
|
||||
- [x] 8. Update OpenSpec artifacts
|
||||
- Mark tasks complete
|
||||
- Add final report note
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- All tasks above are completed.
|
||||
- `npm run typecheck` passes.
|
||||
- `npm run lint` passes.
|
||||
- Tool Workshop and Config Profiles share the same `.config-section` visual style.
|
||||
- Home page cards have comfortable padding on desktop and mobile.
|
||||
- Project repositories are left-aligned with a clear header and an attached "Add Repository" button.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- Do not delete `.form-section`; keep it as an alias.
|
||||
- Do not change project data or behavior; only layout.
|
||||
- Prefer one commit per task area.
|
||||
Reference in New Issue
Block a user