Files
fusion/packages/dashboard/app/components/GitHubImportModal.css
gsxdsm bc2d22df6e feat(dashboard): offer AI translation in Import Tasks preview (#2128)
## Summary

Import Tasks can now offer on-demand AI translation when a selected
GitHub or GitLab issue/PR title and body appear to be in a different
language than the active dashboard locale.

- Detect foreign-language content with a conservative client heuristic
(Unicode scripts + Latin stopwords)
- Show an opt-in banner: **Translate**, then **Show original / Show
translation**, plus **Dismiss**
- Call new `POST /api/ai/translate-text` (shared AI-helper rate limit
with refine/draft)
- Translation is **display-only** in the preview; imported task text
stays the original source language

## Why

Operators working in a non-English dashboard (or reading
non-dashboard-language issues) needed a way to understand import
candidates without leaving the preview or changing what gets imported.

## Test plan

- [x] Unit tests for language detection (`detectContentLanguage`)
- [x] Unit tests for translate request validation, response parsing, and
AI agent path
- [x] GitHub import modal: French content shows translate controls;
English content does not
- [x] Dashboard typecheck clean for app + server packages
- [ ] Manual: open Import Tasks with dashboard language English, select
a French/Korean issue, translate and toggle original
- [ ] Manual: confirm Import still creates the task with original
title/body
- [ ] Manual: dismiss banner for a selection and confirm it stays
dismissed for that item

## Notes

- Comments are not translated (title + body only)
- zh-CN / zh-TW share a CJK family so Chinese content does not prompt
translation when the UI is either Chinese locale
- Secondary locale catalogs have empty placeholders for the new
`git.translate*` keys (runtime falls back to English)
2026-07-15 02:18:43 -07:00

1720 lines
44 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* === GitHub Import Modal ===
Note: Some hardcoded colors below (e.g. #238636, #2ea043, #f0883e, #1f6feb)
are intentional GitHub brand colors and should NOT be replaced with design tokens. */
/* Compact Toolbar Layout */
.github-import-toolbar {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-md) var(--space-lg);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.github-import-toolbar__zone {
display: flex;
align-items: center;
}
.github-import-toolbar__zone--remote {
flex: 0 0 auto;
min-width: 140px;
}
.github-import-toolbar__zone--filter {
flex: 1 1 auto;
min-width: 0;
}
.github-import-toolbar__zone--filter input {
width: 100%;
min-width: 0;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text);
padding: var(--space-sm) var(--space-md);
font-size: 13px;
}
.github-import-toolbar__zone--filter input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
outline: none;
}
.github-import-toolbar__zone--filter input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.github-import-toolbar__zone--filter input::placeholder {
color: var(--text-dim);
}
.github-import-toolbar__zone--action {
flex: 0 0 auto;
}
.github-import-toolbar__loading {
display: flex;
align-items: center;
gap: var(--space-sm);
color: var(--text-muted);
font-size: 13px;
}
.github-import-toolbar__no-remote {
color: var(--text-muted);
font-size: 13px;
font-style: italic;
}
/* Remote pill for single remote */
.github-import-remote-pill {
display: flex;
flex-direction: column;
gap: 2px;
}
.github-import-remote-pill__name {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
}
.github-import-remote-pill__repo {
font-size: 13px;
font-weight: 500;
color: var(--text);
}
/* Remote select dropdown */
.github-import-remote-select select {
min-width: 180px;
padding: var(--space-sm) var(--space-md);
font-size: 13px;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text);
}
.github-import-remote-select select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent);
}
/* Load button */
.github-import-load-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-sm);
min-height: 36px;
padding: var(--space-sm) var(--space-md);
}
.github-import-load-button span {
font-size: 13px;
}
/* Two-pane layout */
.github-import-workspace {
display: flex;
gap: var(--space-sm);
min-height: 0;
flex: 1;
}
.github-import-list-pane,
.github-import-preview-pane {
display: flex;
flex-direction: column;
min-height: 0;
/* FNXC:GitHubImport 2026-06-23-02:00: min-width:0 so neither pane's content can override its flex-basis and force horizontal overflow once GitHub data loads. */
min-width: 0;
}
.github-import-list-pane {
/* Desktop width is controlled by inline flex-basis from the resize handle. */
flex: 0 0 auto;
/* Keep separator ownership on the resize handle to avoid a doubled divider. */
padding-right: var(--space-md);
}
/*
FNXC:GitHubImport 2026-06-23-02:00:
List items must NOT dictate the list pane width. Paired with the pane's min-width:0, every item subtree gets min-width:0 and the title ellipsizes, so a long issue/PR title truncates inside the (resizable) pane instead of forcing it wide.
*/
.github-import-list-pane .issue-main,
.github-import-list-pane .issue-heading-row {
min-width: 0;
}
.github-import-list-pane .issue-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.github-import-list-pane .issue-labels {
flex-wrap: wrap;
overflow: hidden;
}
.github-import-workspace__resize-handle {
position: relative;
flex: 0 0 var(--space-sm);
align-self: stretch;
cursor: col-resize;
touch-action: none;
border-radius: var(--radius-sm);
}
.github-import-workspace__resize-handle::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: var(--space-xs);
transform: translateX(-50%);
border-radius: var(--radius-sm);
background: color-mix(in srgb, var(--border) 35%, transparent);
transition: background var(--transition-fast);
}
.github-import-workspace__resize-handle:hover::before,
.github-import-workspace__resize-handle:active::before {
background: color-mix(in srgb, var(--todo) 50%, transparent);
}
.github-import-workspace__resize-handle:focus-visible {
outline: var(--focus-ring-strong);
outline-offset: 0;
}
.github-import-workspace__resize-handle:focus-visible::before {
background: color-mix(in srgb, var(--todo) 60%, transparent);
}
.github-import-preview-pane {
flex: 1;
min-width: 0;
}
.github-import-pane-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm);
padding-bottom: var(--space-sm);
margin-bottom: var(--space-sm);
border-bottom: 1px solid var(--border);
}
.github-import-pane-header h4 {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.01em;
color: var(--text);
}
/*
FNXC:GitHubImport 2026-06-23-02:00:
Top import action sits at the end of the non-scrolling preview-pane header (space-between pushes it opposite the Preview title / Back button).
margin-left:auto keeps it pinned right even when the mobile Back button is absent. The button height stays compact so the header does not grow taller than the prior single-line heading.
*/
.github-import-action-top {
margin-left: auto;
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: var(--space-xs);
}
/*
FNXC:GitHubImport 2026-06-23-03:15:
Close-issue action sits just left of the top Import action in the preview header. It is the lighter (non-primary) button; flex-shrink:0 keeps it on one line next to Import even on a narrow preview pane.
*/
.github-import-issue-close-top {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: var(--space-xs);
}
/*
FNXC:GitHubImport 2026-06-23-03:15:
Transient inline toast confirming issue close. Sits directly under the preview header; success/error use theme tokens only. Auto-dismisses via component timer.
*/
.github-import-close-toast {
margin-bottom: var(--space-sm);
padding: var(--space-xs) var(--space-sm);
border-radius: var(--radius-sm);
font-size: 12px;
border: 1px solid var(--border);
color: var(--text);
}
.github-import-close-toast--success {
border-color: var(--color-success);
color: var(--color-success);
}
.github-import-close-toast--error {
border-color: var(--color-error);
color: var(--color-error);
}
/*
FNXC:GitHubImportTranslate 2026-07-14-12:00:
Opt-in translation banner for import preview when content language differs from the dashboard locale.
Compact row under metadata so title/body stay readable; error uses the same token language as the close toast.
*/
.github-import-translate {
display: flex;
flex-direction: column;
gap: var(--space-xs);
margin: var(--space-sm) 0;
padding: var(--space-sm) var(--space-md);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: color-mix(in srgb, var(--accent) 8%, var(--surface));
}
.github-import-translate__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-sm);
}
.github-import-translate__icon {
flex: 0 0 auto;
color: var(--accent);
}
.github-import-translate__message {
flex: 1 1 12rem;
min-width: 0;
font-size: 12px;
line-height: 1.4;
color: var(--text);
}
.github-import-translate__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-xs);
margin-left: auto;
}
.github-import-translate__action,
.github-import-translate__dismiss {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
white-space: nowrap;
}
.github-import-translate__error {
font-size: 12px;
color: var(--color-error);
}
.github-import-pane-content {
flex: 1;
min-height: 0;
overflow-y: auto;
}
/* Wider modal for two-pane layout */
.modal.github-import-modal {
width: min(90vw, 1200px);
max-width: 95vw;
min-width: 480px;
height: 80vh;
min-height: 480px;
max-height: calc(100dvh - var(--overlay-padding-top, 10vh) - 16px);
overflow: hidden;
resize: both;
}
.github-import-modal__header {
align-items: flex-start;
}
.github-import-modal__subtitle {
margin-top: var(--space-xs);
color: var(--text-muted);
font-size: 13px;
line-height: 1.5;
}
.github-import-modal__body {
display: flex;
flex-direction: column;
gap: var(--space-md);
padding: var(--space-lg) var(--space-xl);
overflow-y: auto;
min-height: 0;
}
.github-import-modal__actions {
align-items: center;
}
/*
FNXC:ImportTasks 2026-06-22-12:45:
The Import Tasks sub-header tab bar should match the Artifacts view's button bar: plain body row, tokenized border/surface buttons, todo-accent active state, and no card-like filled strip under the main header.
*/
.github-import-tabs {
display: flex;
align-items: center;
gap: var(--space-sm);
padding: 0;
border-bottom: none;
background: transparent;
}
.github-import-tab {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-xs) var(--space-sm);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
color: var(--text-muted);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease;
}
.github-import-tab:hover:not(:disabled) {
background: var(--card-hover);
color: var(--text);
}
.github-import-tab.active {
color: var(--todo);
border-color: var(--todo);
background: color-mix(in srgb, var(--todo) 12%, transparent);
}
.github-import-tab:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.github-import-filter-hint {
color: var(--text-muted);
font-size: 13px;
font-style: italic;
}
.pull-branch-info {
display: block;
margin-top: 2px;
color: var(--text-muted);
font-size: 12px;
font-family: var(--font-mono, monospace);
}
.preview-branch {
margin-bottom: var(--space-sm);
padding: var(--space-sm);
background: var(--surface);
border-radius: var(--radius-sm);
font-size: 12px;
font-family: var(--font-mono, monospace);
}
.preview-branch strong {
color: var(--text-muted);
font-weight: 500;
}
/* Legacy section styles - kept for compatibility */
.github-import-section {
display: flex;
flex-direction: column;
gap: var(--space-md);
padding: var(--space-lg);
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
}
.github-import-section__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--space-md);
}
.github-import-section__header h4 {
font-size: 14px;
font-weight: 600;
letter-spacing: 0.01em;
}
.github-import-section__helper {
margin-top: var(--space-xs);
color: var(--text-muted);
font-size: 13px;
line-height: 1.5;
}
.github-import-repository-pill {
min-width: 180px;
padding: var(--space-sm) var(--space-md);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
text-align: right;
}
.github-import-repository-pill__label {
display: block;
color: var(--text-muted);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.github-import-repository-pill__value {
display: block;
margin-top: 2px;
color: var(--text);
font-size: 13px;
font-weight: 500;
word-break: break-word;
}
.github-import-controls-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(160px, 220px);
gap: var(--space-md);
}
.github-import-form-group {
margin-top: 0;
padding: 0;
}
.github-import-form-group:last-of-type {
margin-bottom: 0;
}
.github-import-form-group--remote {
grid-column: 1 / -1;
}
.github-import-form-group--action {
display: flex;
flex-direction: column;
}
.github-import-remote-card {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
padding: var(--space-md);
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
}
.github-import-remote-card__eyebrow {
color: var(--text-muted);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.github-import-remote-card__title {
margin-top: 2px;
font-size: 14px;
font-weight: 600;
}
.github-import-remote-card__title span {
color: var(--text-muted);
font-weight: 500;
}
.github-import-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4px 10px;
border: 1px solid var(--color-success);
border-radius: var(--radius-lg);
color: var(--color-success);
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.github-import-command {
display: block;
padding: var(--space-sm) var(--space-md);
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text);
font-family: var(--font-mono);
font-size: 12px;
line-height: 1.5;
overflow-x: auto;
}
.form-error {
margin: var(--space-md) 0;
padding: var(--space-sm) var(--space-md);
background: color-mix(in srgb, var(--color-error) 10%, transparent);
border: 1px solid var(--color-error);
border-radius: var(--radius-md);
color: var(--color-error);
font-size: 13px;
line-height: 1.45;
}
.github-import-banner {
margin: 0;
}
.github-import-section--results,
.github-import-section--preview {
min-height: 100%;
}
.github-import-results-meta {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: var(--space-sm);
color: var(--text-muted);
font-size: 12px;
}
.github-import-results-meta span {
padding: var(--space-xs) var(--space-sm);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--surface);
}
.github-import-state {
display: flex;
align-items: flex-start;
gap: var(--space-md);
padding: var(--space-md);
border: 1px dashed var(--border);
border-radius: var(--radius-md);
background: var(--surface);
}
.github-import-state strong {
display: block;
font-size: 13px;
font-weight: 600;
}
.github-import-state span {
display: block;
margin-top: 2px;
color: var(--text-muted);
font-size: 13px;
line-height: 1.5;
}
.github-import-state--loading {
border-style: solid;
}
.github-import-state--warning {
border-style: solid;
}
.github-import-state--error {
border-style: solid;
border-color: var(--color-error);
}
.github-import-state--error strong {
color: var(--color-error);
}
.github-import-state--empty,
.github-import-state--idle {
border-color: var(--border);
}
.issues-list {
display: flex;
flex-direction: column;
max-height: none;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: var(--radius-md);
background: var(--surface);
}
.issue-item {
display: flex;
align-items: flex-start;
gap: var(--space-md);
padding: var(--space-md);
border-bottom: 1px solid var(--border);
cursor: pointer;
transition:
background var(--transition-fast),
border-color var(--transition-fast),
box-shadow var(--transition-fast);
}
.issue-item:last-child {
border-bottom: none;
}
.issue-item input[type="radio"] {
margin-top: 2px;
flex-shrink: 0;
accent-color: var(--in-progress);
}
.issue-item.imported input[type="radio"] {
opacity: 0.5;
cursor: not-allowed;
}
.issue-item:hover:not(.imported) {
background: var(--card-hover);
}
.issue-item.selected {
background: color-mix(in srgb, var(--in-progress) 12%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--in-progress) 35%, transparent);
}
.issue-item.imported {
opacity: 0.65;
cursor: not-allowed;
}
.issue-main {
flex: 1;
min-width: 0;
}
.issue-heading-row {
display: flex;
align-items: baseline;
gap: var(--space-sm);
min-width: 0;
}
.issue-number {
color: var(--text-muted);
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.issue-title {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
font-weight: 500;
}
.issue-labels {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: var(--space-sm);
}
.label-chip {
padding: 3px 8px;
font-size: 11px;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
color: var(--text-muted);
}
.imported-badge {
display: inline-flex;
align-items: center;
justify-content: center;
align-self: center;
padding: 4px 10px;
font-size: 11px;
font-weight: 600;
border: 1px solid var(--color-success);
border-radius: var(--radius-lg);
color: var(--color-success);
text-transform: uppercase;
letter-spacing: 0.08em;
}
.issue-preview {
display: flex;
flex-direction: column;
gap: var(--space-sm);
padding: var(--space-lg);
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
}
.preview-meta {
color: var(--text-muted);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.preview-title {
font-size: 16px;
font-weight: 600;
line-height: 1.35;
}
.preview-body {
font-size: 13px;
color: var(--text-muted);
line-height: 1.6;
white-space: pre-wrap;
word-break: break-word;
}
/*
FNXC:GitHubImport 2026-06-22-18:30:
Full-body preview metadata row (state badge, author, GitHub link) plus the markdown body wrapper.
The markdown variant must NOT pre-wrap/clamp — MailboxMessageContent emits real block elements (p, ul, pre, table), so reset the plain-text white-space and let the body take full height; the preview pane already owns the vertical scroll.
*/
.preview-metadata {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-sm);
font-size: 12px;
color: var(--text-muted);
}
.preview-state-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: 11px;
font-weight: 600;
text-transform: capitalize;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text);
}
.preview-state-badge--open {
color: var(--color-success, var(--accent));
border-color: color-mix(in srgb, var(--color-success, var(--accent)) 40%, transparent);
}
.preview-state-badge--closed {
color: var(--color-error, var(--text-muted));
border-color: color-mix(in srgb, var(--color-error, var(--text-muted)) 40%, transparent);
}
.preview-state-badge--merged {
color: var(--accent);
border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.preview-author {
color: var(--text-muted);
}
.preview-url {
color: var(--accent);
text-decoration: none;
}
.preview-url:hover {
text-decoration: underline;
}
.preview-labels {
display: flex;
flex-wrap: wrap;
gap: var(--space-xs);
}
.preview-body--markdown {
white-space: normal;
word-break: break-word;
color: var(--text);
}
.preview-body--markdown :where(p, ul, ol, pre, table, blockquote, h1, h2, h3, h4) {
margin: 0 0 var(--space-sm);
}
.preview-body--markdown :where(p, ul, ol, pre, table, blockquote, h1, h2, h3, h4):last-child {
margin-bottom: 0;
}
/*
FNXC:GitHubImport 2026-06-23-01:00:
Checks + Comments sections live below the PR body in the scrollable preview pane. Theme tokens only — check pills color via --success/--danger/--warning/--text-muted, mirroring the preview-state-badge token fallbacks.
*/
.github-import-pr-checks,
.github-import-pr-comments {
margin-top: var(--space-md);
padding-top: var(--space-md);
border-top: 1px solid var(--border);
}
.preview-section-heading {
margin: 0 0 var(--space-sm);
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
}
.preview-detail-loading {
display: flex;
align-items: center;
gap: var(--space-xs);
color: var(--text-muted);
font-size: 12px;
}
.preview-detail-error {
color: var(--color-error, var(--text-muted));
font-size: 12px;
}
.preview-detail-empty {
color: var(--text-dim);
font-size: 12px;
}
.github-import-pr-checks__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.github-import-pr-check-row {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.github-import-pr-check-pill {
display: inline-flex;
align-items: center;
flex: 0 0 auto;
min-width: 64px;
justify-content: center;
padding: 2px 8px;
border-radius: var(--radius-sm);
font-size: 10px;
font-weight: 600;
text-transform: capitalize;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text);
}
.github-import-pr-check-pill--success {
color: var(--color-success, var(--accent));
border-color: color-mix(in srgb, var(--color-success, var(--accent)) 40%, transparent);
}
.github-import-pr-check-pill--failure {
color: var(--color-error, var(--text-muted));
border-color: color-mix(in srgb, var(--color-error, var(--text-muted)) 40%, transparent);
}
.github-import-pr-check-pill--pending {
color: var(--color-warning, var(--accent));
border-color: color-mix(in srgb, var(--color-warning, var(--accent)) 40%, transparent);
}
.github-import-pr-check-pill--neutral {
color: var(--text-muted);
border-color: color-mix(in srgb, var(--text-muted) 40%, transparent);
}
.github-import-pr-check-name {
color: var(--text);
text-decoration: none;
word-break: break-word;
}
a.github-import-pr-check-name:hover {
color: var(--accent);
text-decoration: underline;
}
.github-import-pr-comments__list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.github-import-pr-comment {
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: var(--space-sm) var(--space-md);
background: var(--card);
}
.github-import-pr-comment__author {
font-size: 12px;
font-weight: 600;
color: var(--text);
}
.github-import-pr-comment__body {
color: var(--text);
}
/*
FNXC:GitHubImport 2026-06-23-03:30:
Per-comment meta row: avatar, author, human/bot badge, and a readable timestamp. The active comment briefly highlights when reached via prev/next nav.
Across the thread: a top filter (All/Human/Bot) and prev/next chevrons live in the comments header. Theme tokens only.
*/
.github-import-pr-comment__meta {
display: flex;
align-items: center;
gap: var(--space-xs);
margin-bottom: var(--space-xs);
flex-wrap: wrap;
}
.github-import-comment__avatar {
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
flex: 0 0 auto;
border-radius: 50%;
overflow: hidden;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-muted);
}
.github-import-comment__avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.github-import-comment__type-badge {
display: inline-flex;
align-items: center;
gap: 3px;
padding: 1px 6px;
border-radius: var(--radius-sm);
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
background: var(--surface);
border: 1px solid var(--border);
}
.github-import-comment__type-badge--human {
color: var(--text-muted);
border-color: color-mix(in srgb, var(--text-muted) 40%, transparent);
}
.github-import-comment__type-badge--bot {
color: var(--color-warning, var(--accent));
border-color: color-mix(in srgb, var(--color-warning, var(--accent)) 40%, transparent);
}
.github-import-comment__time {
font-size: 11px;
color: var(--text-dim);
margin-left: auto;
}
.github-import-pr-comment--active {
outline: 2px solid var(--accent);
outline-offset: 2px;
transition: outline-color 0.3s ease;
}
.github-import-pr-comments__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-sm);
}
.github-import-comments-nav {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
}
.github-import-comments-nav__pos {
font-size: 11px;
color: var(--text-muted);
min-width: 36px;
text-align: center;
}
.github-import-comments-nav__btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
padding: 0;
border-radius: var(--radius-sm);
background: var(--surface);
border: 1px solid var(--border);
color: var(--text);
cursor: pointer;
}
.github-import-comments-nav__btn:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.github-import-comments-nav__btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.github-import-comments-filter {
display: inline-flex;
align-items: center;
gap: 4px;
margin: 0 0 var(--space-sm);
padding: 2px;
border-radius: var(--radius-md);
background: var(--surface);
border: 1px solid var(--border);
}
.github-import-comments-filter__chip {
padding: 2px 10px;
border: none;
background: transparent;
border-radius: var(--radius-sm);
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
cursor: pointer;
}
.github-import-comments-filter__chip:hover {
color: var(--text);
}
.github-import-comments-filter__chip.active {
background: var(--accent);
color: var(--accent-text, #fff);
}
/* Back button - hidden on desktop by default */
.github-import-back-button {
display: none;
}
/* Light theme overrides for GitHub import components */
[data-theme="light"] .github-import-tabs {
background: var(--bg-secondary);
}
[data-theme="light"] .github-import-tab.active {
background: var(--card);
border-color: var(--border);
box-shadow: var(--shadow-sm);
}
[data-theme="light"] .github-import-toolbar {
background: var(--bg-secondary);
border-bottom-color: var(--border);
}
[data-theme="light"] .github-import-toolbar__zone--filter input {
background: var(--card);
border-color: var(--border);
}
[data-theme="light"] .github-import-toolbar__zone--filter input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--in-progress) 15%, transparent);
}
[data-theme="light"] .issues-list {
background: var(--card);
border-color: var(--border);
}
[data-theme="light"] .issue-item:hover:not(.imported) {
background: var(--card-hover);
}
[data-theme="light"] .issue-item.selected {
background: color-mix(in srgb, var(--in-progress) 8%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--in-progress) 30%, transparent);
}
[data-theme="light"] .issue-item.imported input[type="radio"] {
opacity: 0.4;
}
[data-theme="light"] .github-import-state {
background: var(--bg-secondary);
border-color: var(--border);
}
[data-theme="light"] .issue-preview {
background: var(--bg-secondary);
border-color: var(--border);
}
[data-theme="light"] .label-chip {
background: var(--bg-secondary);
border-color: var(--border);
}
/* Responsive breakpoints */
@media (max-width: 860px) {
/* FNXC:GitHubImport 2026-07-08-00:00: the ENTIRE tablet-band responsive
pane layout below (column-stack, resize-handle hiding, and the
flex:none/max-height:50% caps on the list/preview panes) is for the
dialog presentation only. All selectors are scoped with
:not(.github-import-modal--embedded) so the embedded Import Tasks
surface is governed solely by the base embedded rules + the
@container github-import-embedded blocks below — never by the
viewport. Previously only the width rule was scoped; the unscoped
`.github-import-preview-pane { max-height: 50% }` leaked into the
embedded view on tablet-width viewports (640–860px), clipping a tall
selected issue/PR preview with no way to reach the rest (FN-7694). */
.github-import-modal:not(.github-import-modal--embedded) {
width: calc(100vw - (var(--space-lg) * 2));
}
.github-import-modal:not(.github-import-modal--embedded) .github-import-workspace {
flex-direction: column;
}
.github-import-modal:not(.github-import-modal--embedded) .github-import-workspace__resize-handle {
display: none;
}
.github-import-modal:not(.github-import-modal--embedded) .github-import-list-pane {
flex: none;
border-right: none;
border-bottom: 1px solid var(--border);
padding-right: 0;
padding-bottom: var(--space-md);
max-height: 50%;
}
.github-import-modal:not(.github-import-modal--embedded) .github-import-preview-pane {
flex: none;
max-height: 50%;
}
}
@media (max-width: 640px) {
/* Full-screen sheet on mobile — drop overlay padding so the modal
actually fills the viewport instead of being pushed below it.
FNXC:GitHubImport 2026-06-22-16:00: scope the viewport-takeover to the
NON-embedded (dialog) presentation via :not(.github-import-modal--embedded).
The embedded view lives inside the main-content pane (between the mobile
Header and MobileNavBar); without the guard its base .github-import-modal
class matched these 100vw/100dvh rules and covered the whole screen. The
embedded panel keeps its own 100%-of-pane sizing from the embedded block
below. */
.modal-overlay:has(.github-import-modal:not(.github-import-modal--embedded)) {
padding-top: 0;
align-items: stretch;
justify-content: stretch;
}
.modal.github-import-modal:not(.github-import-modal--embedded) {
width: 100vw;
min-width: 0;
max-width: 100vw;
height: 100dvh;
min-height: 0;
max-height: 100dvh;
margin: 0;
border: none;
border-radius: 0;
resize: none;
}
.github-import-modal__body {
flex: 1;
min-height: 0;
overflow: hidden;
padding: var(--space-md);
gap: var(--space-md);
}
.github-import-toolbar {
flex-wrap: wrap;
gap: var(--space-sm);
padding: var(--space-sm);
}
.github-import-toolbar__zone--remote {
flex: 1 1 100%;
order: 1;
}
.github-import-toolbar__zone--filter {
flex: 1 1 auto;
order: 2;
min-width: 140px;
}
.github-import-toolbar__zone--action {
flex: 0 0 auto;
order: 3;
}
.github-import-remote-select select {
width: 100%;
min-width: 0;
}
.github-import-section {
padding: var(--space-md);
}
.github-import-section__header {
flex-direction: column;
}
.github-import-repository-pill {
width: 100%;
min-width: 0;
text-align: left;
}
.github-import-controls-grid {
grid-template-columns: 1fr;
}
.github-import-results-meta {
justify-content: flex-start;
}
.issue-item {
flex-wrap: wrap;
min-height: 36px;
}
.imported-badge {
align-self: flex-start;
margin-left: 28px;
}
.issues-list {
max-height: 50vh;
}
.github-import-workspace {
flex: 1;
min-height: 0;
overflow: hidden;
}
/* Mobile pane visibility - show one at a time */
.github-import-list-pane.mobile {
display: none;
}
.github-import-list-pane.mobile.active {
display: flex;
flex: 1;
border-right: none;
padding-right: 0;
max-height: none;
}
.github-import-preview-pane.mobile {
display: none;
}
.github-import-preview-pane.mobile.active {
display: flex;
flex: 1;
min-height: 0;
max-height: none;
overflow: hidden;
}
.github-import-preview-pane.mobile.active .github-import-pane-content {
flex: 1;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
}
/*
FNXC:GitHubImport 2026-06-23-02:45:
Narrow-mobile (viewport <= 640px) EMBEDDED scroll fix. isMobile is viewport-derived, so at <= 640px the
embedded Import Tasks view also enters mobile single-pane mode and inherits the @640 nested-scroll design
intended for the dialog (workspace/pane `overflow: hidden`, pane-content `overflow-y: auto`). But the
embedded block (later in source) flips the preview pane + its content to natural height + `overflow-y: visible`
so the VIEW BODY is meant to be the scroll owner — leaving the @640 `overflow: hidden` on the workspace and
the active preview pane as orphaned clips with no inner scroll owner, so a tall preview (body, checks, comments)
was cut off with no way to reach it.
Fix: in narrow embedded mode make `.github-import-modal__body` the single scroll owner (flex:1; overflow-y:auto)
and give every ancestor in the chain (workspace, the active list/preview panes, pane-content) natural height with
`overflow: visible` so nothing traps or clips the growing content. The list keeps its own `.issues-list`
internal scroll. Wide (>=720px container) two-pane behavior and the dialog (non-embedded) path are untouched —
this rule is scoped to `.github-import-modal--embedded`.
*/
.github-import-modal--embedded .github-import-modal__body {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
}
.github-import-modal--embedded .github-import-workspace {
flex: 0 0 auto;
min-height: 0;
overflow: visible;
}
.github-import-modal--embedded .github-import-list-pane.mobile.active,
.github-import-modal--embedded .github-import-preview-pane.mobile.active {
flex: 0 0 auto;
max-height: none;
overflow: visible;
}
/* Single scroll owner: let the list grow with the page instead of owning a nested scroll. */
.github-import-modal--embedded .github-import-list-pane.mobile.active .issues-list {
max-height: none;
overflow-y: visible;
}
.github-import-modal--embedded .github-import-preview-pane.mobile.active .github-import-pane-content {
flex: 0 0 auto;
min-height: auto;
overflow-y: visible;
overscroll-behavior: auto;
}
/* Back button styles */
.github-import-back-button {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
padding: var(--space-sm) var(--space-md);
background: transparent;
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text);
font-size: 13px;
cursor: pointer;
transition: background var(--transition-fast);
}
.github-import-back-button:hover {
background: var(--card-hover);
}
.github-import-back-button:focus {
outline: none;
box-shadow: var(--focus-ring);
}
.github-import-pane-header:has(.github-import-back-button) {
justify-content: flex-start;
gap: var(--space-md);
}
}
@keyframes github-import-spinner-spin {
to {
transform: rotate(360deg);
}
}
/*
FNXC:RightDockEmbedding 2026-06-22-00:00:
Right-dock redesign renders the GitHub import surface inline in the main content area instead of as a fixed popup overlay.
The embedded root is a plain flow box that fills the host; the inner shell sheds overlay-only chrome (fixed sizing, box-shadow, rounded corners, resize) and fills 100% so the main panel owns the frame. No close button is rendered in embedded mode.
*/
.github-import-embedded.right-dock-embedded-view {
display: flex;
width: 100%;
height: 100%;
min-height: 0;
/*
FNXC:ImportTasks 2026-06-22-16:05:
Import Tasks is a full main-content view, not a modal card. Match Skills/other view bodies by letting the host read as the dashboard background while the shared header owns the surface band.
*/
background: var(--bg);
}
/*
FNXC:ViewHeader 2026-06-23-04:15:
Embedded root drops its uniform --space-lg padding so the header can span edge-to-edge like the canonical ViewHeader; the body re-applies a horizontal inset below. This keeps the resizable list/preview/comments work intact (only the outer padding moves to the body).
*/
.github-import-modal.github-import-modal--embedded {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
max-width: none;
min-width: 0;
max-height: none;
min-height: 0;
position: static;
box-shadow: none;
border-radius: 0;
resize: none;
padding: 0;
background: var(--bg);
border: none;
}
/*
FNXC:ViewHeader 2026-06-23-04:15:
Import Tasks embedded header now adopts the canonical ViewHeader chrome — edge-to-edge --surface bg, no bottom divider, --space-lg/--space-xl padding, and the shared --view-header-min-height (≈61px border-box) — so it matches Agents/Mailbox/Missions/Automations height + padding exactly. (Previously a plain padding-bottom title row with no surface/min-height.)
*/
.github-import-modal__embedded-header {
box-sizing: border-box;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
gap: var(--space-md);
min-height: var(--view-header-min-height);
padding: var(--space-lg) var(--space-xl);
background: var(--surface);
border-bottom: none;
}
.github-import-modal__embedded-title {
display: flex;
align-items: center;
gap: var(--space-sm);
min-width: 0;
margin: 0;
font-size: 1.125rem;
font-weight: 600;
color: var(--text);
}
/* FNXC:ViewHeader 2026-06-23-04:15: Canonical --todo leading-icon tint at size 20, matching every other view header. */
.github-import-modal__embedded-title svg {
flex-shrink: 0;
color: var(--todo);
}
/* Body re-applies the horizontal + bottom inset the now-edge-to-edge header no longer provides. */
.github-import-modal--embedded .github-import-modal__body {
padding: var(--space-lg) var(--space-xl) var(--space-lg);
background: var(--bg);
}
/*
FNXC:RightDockEmbedding 2026-06-22-12:30:
Embedded "Import Tasks" main-content view must fit on screen and react to its OWN width, not the viewport.
The shared two-pane code sets the list pane width via an inline flex-basis (default 360px) whenever the VIEWPORT is wide (canResizePanes => innerWidth > 860). In the embedded main area the host can be far narrower than the viewport, so that inline 360px list pane plus the preview overflowed horizontally.
Fix (embedded variant only — modal path untouched): turn the embedded root into a query container (container-type: inline-size) and drive the layout off @container width.
- Narrow container (default): stack list ABOVE preview in a single column; cap the list height and override the inline desktop flex-basis so nothing forces horizontal overflow. Long titles/repo names already truncate via .issue-title ellipsis / .issue-main min-width:0, and labels/branch info wrap.
- Wide container (>= 720px): restore the two-pane row, but bound the list pane to a sane share of the container (clamp) instead of trusting the viewport-derived inline width.
*/
.github-import-embedded.right-dock-embedded-view {
container-type: inline-size;
container-name: github-import-embedded;
}
/*
FNXC:RightDockEmbedding 2026-06-22-01:00:
Embedded Import Tasks must scroll vertically so a long preview is fully reachable. The view body is the scroll container; in the stacked (narrow) layout the preview takes its natural (content) height and the body scrolls, so the preview can be much taller than the viewport. In the wide two-pane layout the preview keeps its own internal scroll.
FNXC:GitHubImport 2026-06-28-00:00:
Issues and Pull Requests share this embedded body/workspace chain, so the body must flex-fill the main-content pane at all widths. Keeping the fix scoped to `.github-import-modal--embedded` preserves the non-embedded dialog path byte-for-byte while eliminating blank space below short/idle workspaces.
*/
.github-import-modal--embedded .github-import-modal__body {
flex: 1;
min-height: 0;
overflow-y: auto;
}
/*
FNXC:RightDockEmbedding 2026-06-22-13:30:
Default (narrow container): single stacked column. The workspace takes its natural (content) height
(flex: 0 0 auto) rather than the shared `flex: 1`, so the tall preview can push the workspace past the
body height and .github-import-modal__body scrolls to reveal it. The list pane stays internally capped.
*/
.github-import-modal--embedded .github-import-workspace {
flex-direction: column;
flex: 0 0 auto;
}
/* Override the inline viewport-derived flex-basis so the list never forces overflow when stacked. */
.github-import-modal--embedded .github-import-list-pane {
flex: 0 0 auto !important;
width: 100%;
max-height: 40cqh;
padding-right: 0;
}
.github-import-modal--embedded .github-import-preview-pane {
flex: 0 0 auto;
width: 100%;
min-width: 0;
}
/*
FNXC:RightDockEmbedding 2026-06-22-13:30:
Stacked (narrow) embedded layout: the preview pane is natural-height (flex: 0 0 auto), so its inner
.github-import-pane-content must NOT keep the side-by-side `flex: 1; min-height: 0; overflow-y: auto`
treatment — under a natural-height parent that collapses the content to ~zero and clips the issue/PR
body. Let the content take its full intrinsic height and hand vertical scrolling to .github-import-modal__body
(the view scroll owner) so a long, tall preview is fully reachable by scrolling the whole view.
*/
.github-import-modal--embedded .github-import-preview-pane .github-import-pane-content {
flex: 0 0 auto;
min-height: auto;
overflow-y: visible;
}
/* Hide the col-resize handle when stacked; it only makes sense in the side-by-side layout. */
.github-import-modal--embedded .github-import-workspace__resize-handle {
display: none;
}
@container github-import-embedded (min-width: 720px) {
.github-import-modal--embedded .github-import-workspace {
flex-direction: row;
/* Wide layout: fill the body height so the preview pane can scroll internally (not the whole view). */
flex: 1 1 auto;
}
/*
FNXC:GitHubImport 2026-06-23-00:30:
Wide (two-pane) embedded layout: the list pane must honor the user's inline `flex: 0 0 <listPaneWidth>px` from the resize
handle — the prior `flex: ... !important` clamp silently overrode it, which is why the embedded Import Tasks list "couldn't
be made smaller" and the preview stayed cramped. Reset only the stacked-mode overrides (width/max-height/padding) here and let
the inline flex win. The preview pane is `flex: 1 1 auto; min-width: 0` (below) so the freed width flows to the preview.
*/
.github-import-modal--embedded .github-import-list-pane {
/*
FNXC:GitHubImport 2026-06-23-02:00:
min-width:0 is ESSENTIAL. Without it the flex item keeps min-width:auto, so once the GitHub data loads the wide issue/PR titles (large min-content width) override the 256px flex-basis and blow the pane out to ~978px, pushing the workspace into horizontal overflow — the user's "data loads → view too wide → can't make it smaller". With min-width:0 the inline flex-basis is honored and the list content truncates within the pane instead of dictating its width.
`!important` only neutralizes the stacked rule's own `!important`; the width comes from the inline CSS var.
*/
flex: 0 0 var(--gh-import-list-width, clamp(160px, 30cqi, 480px)) !important;
min-width: 0 !important;
width: auto;
max-height: none;
padding-right: var(--space-md);
}
/*
FNXC:RightDockEmbedding 2026-06-22-13:30:
Wide (two-pane) embedded layout: the preview pane fills the available row height and scrolls
INTERNALLY so a long preview is reachable without moving the list. Restore the flex-fill pane and
let .github-import-pane-content own the vertical scroll (min-height:0 enables the overflow inside a
flex child). The list keeps its own internal scroll, so the two panes scroll independently.
*/
.github-import-modal--embedded .github-import-preview-pane {
flex: 1 1 auto;
min-height: 0;
}
.github-import-modal--embedded .github-import-preview-pane .github-import-pane-content {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
}
.github-import-modal--embedded .github-import-workspace__resize-handle {
display: block;
}
}
/*
FNXC:GitLabImport 2026-07-02-00:00:
FN-7424 keeps GitLab project issue, group issue, and merge request imports inside the existing Import Tasks surface. Use the established tab, toolbar, list, and preview classes so desktop and mobile affordances do not create a second navigation destination or hidden button shell.
*/
.github-import-provider {
display: flex;
gap: var(--spacing-2);
padding: var(--spacing-3) var(--spacing-4) 0;
}
.github-import-gitlab {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
min-height: 0;
flex: 1;
}
.github-import-gitlab__workspace {
display: grid;
grid-template-columns: minmax(0, var(--import-list-width, 16rem)) minmax(0, 1fr);
gap: var(--spacing-3);
min-height: 0;
flex: 1;
}
.github-import-gitlab .github-import-preview-pane {
min-width: 0;
}
@media (max-width: 768px) {
.github-import-provider,
.github-import-gitlab .github-import-toolbar {
flex-wrap: wrap;
}
.github-import-gitlab__workspace {
grid-template-columns: 1fr;
}
}