[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
7
infrastructure/config/appIconVariants.test.ts
Normal file
7
infrastructure/config/appIconVariants.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { test } from 'node:test';
|
||||
import { APP_ICON_VARIANT_ASSET_PATH } from './appIconVariants';
|
||||
|
||||
test('original icon preview uses the desktop-sized runtime asset', () => {
|
||||
assert.equal(APP_ICON_VARIANT_ASSET_PATH.original, '/icons/variants/original.png');
|
||||
});
|
||||
86
infrastructure/config/appIconVariants.ts
Normal file
86
infrastructure/config/appIconVariants.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
APP_ICON_VARIANTS,
|
||||
DEFAULT_APP_ICON_VARIANT,
|
||||
type AppIconVariant,
|
||||
} from '../../domain/appIconVariant';
|
||||
|
||||
export { APP_ICON_VARIANTS, DEFAULT_APP_ICON_VARIANT, type AppIconVariant };
|
||||
|
||||
// Elf SVG icon source — shared across all variants.
|
||||
export const APP_ICON_ELF_SVG = '/senmesh-elf-logo.svg';
|
||||
|
||||
// Background colors for the icon preview tiles in Settings.
|
||||
// Each variant is: background (can be a linear-gradient or solid color), optional border.
|
||||
export const APP_ICON_VARIANT_TILE: Record<
|
||||
AppIconVariant,
|
||||
{ background: string; border?: string }
|
||||
> = {
|
||||
original: { background: 'linear-gradient(135deg, #5BAF7E 0%, #3D8B5E 100%)' },
|
||||
bright: { background: 'linear-gradient(135deg, #A8E0B0 0%, #7BC48A 100%)' },
|
||||
dark: { background: 'linear-gradient(135deg, #2E7D4F 0%, #1B5E3A 100%)' },
|
||||
colorful: { background: 'linear-gradient(135deg, #5BAF7E 0%, #7BC4A5 50%, #A8D8B9 100%)' },
|
||||
'high-contrast': { background: '#0F2418', border: '1px solid #5BAF7E' },
|
||||
'white-navy': { background: 'linear-gradient(135deg, #1E3A5F 0%, #0F2440 100%)' },
|
||||
'white-sky': { background: 'linear-gradient(135deg, #7EC8E3 0%, #4A9BC0 100%)' },
|
||||
'white-rose': { background: 'linear-gradient(135deg, #F8BBD0 0%, #E8A0B8 100%)' },
|
||||
'white-emerald': { background: 'linear-gradient(135deg, #5BAF7E 0%, #3D8B5E 100%)' },
|
||||
'white-amber': { background: 'linear-gradient(135deg, #F5CBA7 0%, #E0A86A 100%)' },
|
||||
'white-violet': { background: 'linear-gradient(135deg, #9B7FC4 0%, #6D4F99 100%)' },
|
||||
rainbow: {
|
||||
background: 'linear-gradient(135deg, #E74C3C 0%, #F39C12 20%, #F1C40F 40%, #27AE60 60%, #3498DB 80%, #9B59B6 100%)',
|
||||
},
|
||||
};
|
||||
|
||||
// Legacy PNG paths — kept for Electron main-process window icon usage
|
||||
// (real PNG files are still needed for taskbar/dock icons).
|
||||
export const APP_ICON_VARIANT_ASSET_PATH: Record<AppIconVariant, string> = {
|
||||
original: '/icons/variants/original.png',
|
||||
bright: '/icons/variants/bright.png',
|
||||
dark: '/icons/variants/dark.png',
|
||||
colorful: '/icons/variants/colorful.png',
|
||||
'high-contrast': '/icons/variants/high-contrast.png',
|
||||
'white-navy': '/icons/variants/white-navy.png',
|
||||
'white-sky': '/icons/variants/white-sky.png',
|
||||
'white-rose': '/icons/variants/white-rose.png',
|
||||
'white-emerald': '/icons/variants/white-emerald.png',
|
||||
'white-amber': '/icons/variants/white-amber.png',
|
||||
'white-violet': '/icons/variants/white-violet.png',
|
||||
rainbow: '/icons/variants/rainbow.png',
|
||||
};
|
||||
|
||||
export const APP_ICON_VARIANT_I18N_KEY: Record<AppIconVariant, string> = {
|
||||
original: 'settings.appearance.appIcon.original',
|
||||
bright: 'settings.appearance.appIcon.bright',
|
||||
dark: 'settings.appearance.appIcon.dark',
|
||||
colorful: 'settings.appearance.appIcon.colorful',
|
||||
'high-contrast': 'settings.appearance.appIcon.highContrast',
|
||||
'white-navy': 'settings.appearance.appIcon.whiteNavy',
|
||||
'white-sky': 'settings.appearance.appIcon.whiteSky',
|
||||
'white-rose': 'settings.appearance.appIcon.whiteRose',
|
||||
'white-emerald': 'settings.appearance.appIcon.whiteEmerald',
|
||||
'white-amber': 'settings.appearance.appIcon.whiteAmber',
|
||||
'white-violet': 'settings.appearance.appIcon.whiteViolet',
|
||||
rainbow: 'settings.appearance.appIcon.rainbow',
|
||||
};
|
||||
|
||||
export const APP_ICON_VARIANT_GROUPS: Array<{
|
||||
id: 'classic' | 'white' | 'special';
|
||||
labelKey: string;
|
||||
variants: AppIconVariant[];
|
||||
}> = [
|
||||
{
|
||||
id: 'classic',
|
||||
labelKey: 'settings.appearance.appIcon.group.classic',
|
||||
variants: ['original', 'bright', 'dark', 'colorful', 'high-contrast'],
|
||||
},
|
||||
{
|
||||
id: 'white',
|
||||
labelKey: 'settings.appearance.appIcon.group.white',
|
||||
variants: ['white-navy', 'white-sky', 'white-rose', 'white-emerald', 'white-amber', 'white-violet'],
|
||||
},
|
||||
{
|
||||
id: 'special',
|
||||
labelKey: 'settings.appearance.appIcon.group.special',
|
||||
variants: ['rainbow'],
|
||||
},
|
||||
];
|
||||
315
infrastructure/config/cjkFonts.test.ts
Normal file
315
infrastructure/config/cjkFonts.test.ts
Normal file
@@ -0,0 +1,315 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
composeFontFamilyStack,
|
||||
getDefaultCjkFallback,
|
||||
getRecommendedCjkFor,
|
||||
splitFontFamilyList,
|
||||
CJK_SYSTEM_FALLBACK_STACK,
|
||||
} from './cjkFonts';
|
||||
|
||||
describe('composeFontFamilyStack', () => {
|
||||
it('puts the primary font first', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo, monospace',
|
||||
userFallback: '',
|
||||
latinFontId: 'menlo',
|
||||
platform: 'darwin',
|
||||
});
|
||||
assert.match(stack, /^Menlo,\s*/);
|
||||
});
|
||||
|
||||
it('inserts user fallback right after primary when provided', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"Fira Code", monospace',
|
||||
userFallback: 'Sarasa Mono SC',
|
||||
latinFontId: 'fira-code',
|
||||
platform: 'darwin',
|
||||
});
|
||||
const firaIdx = stack.indexOf('Fira Code');
|
||||
const userIdx = stack.indexOf('Sarasa Mono SC');
|
||||
assert.ok(firaIdx >= 0 && userIdx > firaIdx, 'user fallback after primary');
|
||||
});
|
||||
|
||||
it('uses per-Latin-font recommended CJK when user fallback is empty', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"Cascadia Code", monospace',
|
||||
userFallback: '',
|
||||
latinFontId: 'cascadia-code',
|
||||
platform: 'win32',
|
||||
});
|
||||
// Cascadia Code now recommends Sarasa Mono SC (true monospace).
|
||||
assert.match(stack, /Sarasa Mono SC/);
|
||||
});
|
||||
|
||||
it('falls back to OS default when Latin font has no recommendation', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"Unknown Font", monospace',
|
||||
userFallback: '',
|
||||
latinFontId: 'unknown',
|
||||
platform: 'darwin',
|
||||
});
|
||||
// macOS no-recommendation default is now Sarasa Mono SC (bundled).
|
||||
assert.match(stack, /Sarasa Mono SC/);
|
||||
});
|
||||
|
||||
it('quotes multi-word user fallback names', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo, monospace',
|
||||
userFallback: 'Source Han Mono SC',
|
||||
latinFontId: 'menlo',
|
||||
platform: 'linux',
|
||||
});
|
||||
assert.match(stack, /"Source Han Mono SC"/);
|
||||
});
|
||||
|
||||
it('keeps an unquoted user fallback containing a comma as one font family', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo, monospace',
|
||||
userFallback: 'Foo, Inc. Mono',
|
||||
latinFontId: 'menlo',
|
||||
platform: 'darwin',
|
||||
});
|
||||
|
||||
assert.ok(stack.includes('"Foo, Inc. Mono"'));
|
||||
assert.ok(splitFontFamilyList(stack).includes('"Foo, Inc. Mono"'));
|
||||
});
|
||||
|
||||
it('escapes quotes inside a manually entered font family', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo, monospace',
|
||||
userFallback: 'Foo"Bar',
|
||||
latinFontId: 'menlo',
|
||||
platform: 'darwin',
|
||||
});
|
||||
|
||||
assert.ok(stack.includes('"Foo\\"Bar"'));
|
||||
assert.ok(splitFontFamilyList(stack).includes('"Foo\\"Bar"'));
|
||||
});
|
||||
|
||||
it('quotes a manually entered font family containing an apostrophe', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo, monospace',
|
||||
userFallback: "Rock'nRoll",
|
||||
latinFontId: 'menlo',
|
||||
platform: 'darwin',
|
||||
});
|
||||
|
||||
assert.ok(stack.includes('"Rock\'nRoll"'));
|
||||
assert.ok(splitFontFamilyList(stack).includes('"Rock\'nRoll"'));
|
||||
});
|
||||
|
||||
it('quotes a manually entered font family that starts with a digit', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo, monospace',
|
||||
userFallback: '3270font',
|
||||
latinFontId: 'menlo',
|
||||
platform: 'darwin',
|
||||
});
|
||||
|
||||
assert.ok(stack.includes('"3270font"'));
|
||||
assert.ok(splitFontFamilyList(stack).includes('"3270font"'));
|
||||
});
|
||||
|
||||
it('keeps CSS generic font families unquoted', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo, monospace',
|
||||
userFallback: 'ui-monospace',
|
||||
latinFontId: 'menlo',
|
||||
platform: 'darwin',
|
||||
});
|
||||
|
||||
assert.ok(splitFontFamilyList(stack).includes('ui-monospace'));
|
||||
});
|
||||
|
||||
it('does not duplicate identical fallback entries', () => {
|
||||
// User explicitly picks the same font the per-font pairing would,
|
||||
// and that font also lives in the system stack — should appear once.
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"Cascadia Code", monospace',
|
||||
userFallback: 'Sarasa Mono SC',
|
||||
latinFontId: 'cascadia-code',
|
||||
platform: 'win32',
|
||||
});
|
||||
const matches = stack.match(/Sarasa Mono SC/g) || [];
|
||||
assert.equal(matches.length, 1);
|
||||
});
|
||||
|
||||
it('inserts JetBrains Mono as Latin-only fallback right after the primary family', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: 'Menlo',
|
||||
userFallback: '',
|
||||
latinFontId: 'menlo',
|
||||
platform: 'darwin',
|
||||
});
|
||||
const families = stack.split(',').map((s) => s.trim().replace(/^"|"$/g, ''));
|
||||
assert.equal(families[0], 'Menlo');
|
||||
assert.equal(families[1], 'JetBrains Mono');
|
||||
});
|
||||
|
||||
it('Latin fallback (JetBrains Mono) precedes every CJK family', () => {
|
||||
// Regression guard for codex P1 review on PR #940 (first round):
|
||||
// when the primary font isn't installed, Latin glyphs must fall to
|
||||
// a Latin-only monospace face — NOT a CJK font's full-width Latin
|
||||
// variant — to keep xterm's fixed cell grid aligned. JetBrains Mono
|
||||
// is bundled via @fontsource and contains no CJK glyphs, so it
|
||||
// catches Latin while letting CJK glyphs flow past.
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"Fira Code", monospace',
|
||||
userFallback: 'LXGW WenKai Mono',
|
||||
latinFontId: 'fira-code',
|
||||
platform: 'darwin',
|
||||
});
|
||||
const jbIdx = stack.indexOf('JetBrains Mono');
|
||||
const sarasaIdx = stack.indexOf('Sarasa Mono SC');
|
||||
const userFallbackIdx = stack.indexOf('LXGW WenKai Mono');
|
||||
const simSunIdx = stack.indexOf('SimSun');
|
||||
assert.ok(jbIdx > 0, 'JetBrains Mono must appear in the stack');
|
||||
assert.ok(jbIdx < userFallbackIdx, 'JetBrains Mono before user CJK');
|
||||
assert.ok(jbIdx < sarasaIdx, 'JetBrains Mono before Sarasa system fallback');
|
||||
assert.ok(jbIdx < simSunIdx, 'JetBrains Mono before SimSun system fallback');
|
||||
});
|
||||
|
||||
it('preserves a quoted primary family name that contains a comma', () => {
|
||||
// Regression guard for codex P2 review on PR #940: when the primary
|
||||
// family is something like `"Foo, Inc. Mono"`, the composed stack
|
||||
// must keep that token intact rather than splitting on the internal
|
||||
// comma and emitting fragmented pieces.
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"Foo, Inc. Mono", monospace',
|
||||
userFallback: '',
|
||||
latinFontId: 'foo-inc-mono',
|
||||
platform: 'darwin',
|
||||
});
|
||||
assert.ok(
|
||||
stack.includes('"Foo, Inc. Mono"'),
|
||||
'quoted family with comma stays a single token',
|
||||
);
|
||||
assert.ok(
|
||||
!stack.includes('"Foo,') || stack.includes('"Foo, Inc. Mono"'),
|
||||
'must not produce a dangling `"Foo,` fragment',
|
||||
);
|
||||
});
|
||||
|
||||
it('user-chosen CJK fallback precedes generic monospace', () => {
|
||||
// Regression guard for codex P1 review on PR #940 (second round):
|
||||
// generic `monospace` on macOS Chrome resolves Chinese glyphs to
|
||||
// PingFang via Chromium's CJK system fallback. If `monospace`
|
||||
// appeared in the chain BEFORE the user's CJK pick, CSS per-glyph
|
||||
// fallback would stop at monospace for CJK characters and never
|
||||
// consult the user's choice, silently nullifying the CJK picker.
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"Fira Code", monospace',
|
||||
userFallback: 'LXGW WenKai Mono',
|
||||
latinFontId: 'fira-code',
|
||||
platform: 'darwin',
|
||||
});
|
||||
const userFallbackIdx = stack.indexOf('LXGW WenKai Mono');
|
||||
// Match `monospace` as a standalone token (after the comma+space).
|
||||
const monospaceIdx = stack.lastIndexOf(', monospace');
|
||||
assert.ok(userFallbackIdx > 0, 'user CJK must appear');
|
||||
assert.ok(monospaceIdx > userFallbackIdx, 'user CJK must come before generic monospace');
|
||||
});
|
||||
|
||||
it('explicit user fallback overrides the per-font recommendation', () => {
|
||||
const stack = composeFontFamilyStack({
|
||||
primaryFamily: '"JetBrains Mono", monospace',
|
||||
userFallback: 'LXGW WenKai Mono',
|
||||
latinFontId: 'jetbrains-mono',
|
||||
platform: 'darwin',
|
||||
});
|
||||
// User chose LXGW WenKai Mono; the JetBrains Mono recommendation
|
||||
// (Sarasa Mono SC) should be suppressed, so Sarasa only shows up
|
||||
// later in the system fallback stack, AFTER the user choice.
|
||||
const userIdx = stack.indexOf('LXGW WenKai Mono');
|
||||
const sarasaIdx = stack.indexOf('Sarasa Mono SC');
|
||||
assert.ok(userIdx >= 0);
|
||||
assert.ok(sarasaIdx > userIdx, 'system Sarasa appears after explicit user choice');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultCjkFallback', () => {
|
||||
it('returns SimSun on Windows (always installed, monospace)', () => {
|
||||
assert.equal(getDefaultCjkFallback('win32'), 'SimSun');
|
||||
});
|
||||
it('returns Sarasa Mono SC on macOS (bundled by app)', () => {
|
||||
assert.equal(getDefaultCjkFallback('darwin'), 'Sarasa Mono SC');
|
||||
});
|
||||
it('returns Noto Sans Mono CJK SC on Linux', () => {
|
||||
assert.equal(getDefaultCjkFallback('linux'), 'Noto Sans Mono CJK SC');
|
||||
});
|
||||
it('never returns a known proportional font', () => {
|
||||
const proportional = ['PingFang SC', 'Microsoft YaHei UI', 'Microsoft YaHei', 'Hiragino Sans GB'];
|
||||
for (const platform of ['darwin', 'win32', 'linux'] as const) {
|
||||
const v = getDefaultCjkFallback(platform);
|
||||
assert.ok(!proportional.includes(v), `${platform} default ${v} must not be proportional`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRecommendedCjkFor', () => {
|
||||
it('returns null for unknown fonts', () => {
|
||||
assert.equal(getRecommendedCjkFor('unknown-font-id', 'darwin'), null);
|
||||
});
|
||||
it('returns a non-empty string for known fonts', () => {
|
||||
const v = getRecommendedCjkFor('jetbrains-mono', 'darwin');
|
||||
assert.ok(v && v.length > 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('splitFontFamilyList', () => {
|
||||
it('splits a simple comma-separated list', () => {
|
||||
assert.deepEqual(
|
||||
splitFontFamilyList('Menlo, monospace'),
|
||||
['Menlo', 'monospace'],
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps quoted family names with commas intact', () => {
|
||||
// Regression guard for codex P2 review on PR #940: a font family
|
||||
// name like `"Foo, Inc. Mono"` is a single token in CSS, not two.
|
||||
assert.deepEqual(
|
||||
splitFontFamilyList('"Foo, Inc. Mono", monospace'),
|
||||
['"Foo, Inc. Mono"', 'monospace'],
|
||||
);
|
||||
});
|
||||
|
||||
it('handles a single unquoted name', () => {
|
||||
assert.deepEqual(splitFontFamilyList('Iosevka'), ['Iosevka']);
|
||||
});
|
||||
|
||||
it('handles single quotes too', () => {
|
||||
assert.deepEqual(
|
||||
splitFontFamilyList("'Foo, Inc.', serif"),
|
||||
["'Foo, Inc.'", 'serif'],
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps escaped quotes inside a quoted family name', () => {
|
||||
assert.deepEqual(
|
||||
splitFontFamilyList('"Foo\\"Bar", monospace'),
|
||||
['"Foo\\"Bar"', 'monospace'],
|
||||
);
|
||||
});
|
||||
|
||||
it('drops empty segments produced by double commas', () => {
|
||||
assert.deepEqual(
|
||||
splitFontFamilyList('Menlo,, monospace'),
|
||||
['Menlo', 'monospace'],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CJK_SYSTEM_FALLBACK_STACK', () => {
|
||||
it('contains true-monospace CJK fonts only', () => {
|
||||
assert.match(CJK_SYSTEM_FALLBACK_STACK, /Sarasa Mono SC/);
|
||||
assert.match(CJK_SYSTEM_FALLBACK_STACK, /Noto Sans Mono CJK SC/);
|
||||
assert.match(CJK_SYSTEM_FALLBACK_STACK, /SimSun/);
|
||||
});
|
||||
|
||||
it('does not include known proportional CJK fonts', () => {
|
||||
assert.doesNotMatch(CJK_SYSTEM_FALLBACK_STACK, /PingFang SC/);
|
||||
assert.doesNotMatch(CJK_SYSTEM_FALLBACK_STACK, /Microsoft YaHei UI/);
|
||||
assert.doesNotMatch(CJK_SYSTEM_FALLBACK_STACK, /Hiragino Sans GB/);
|
||||
});
|
||||
});
|
||||
205
infrastructure/config/cjkFonts.ts
Normal file
205
infrastructure/config/cjkFonts.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
export type SupportedPlatform = 'darwin' | 'win32' | 'linux' | (string & {});
|
||||
|
||||
// True monospace CJK fonts only. Proportional fonts (PingFang SC,
|
||||
// Microsoft YaHei UI, Hiragino Sans GB) render at non-2x widths in a
|
||||
// terminal grid — including them here visibly broke alignment for users
|
||||
// whose primary font lacked CJK glyphs. They are intentionally absent.
|
||||
const CJK_SYSTEM_FALLBACK_FONTS = [
|
||||
'"Sarasa Mono SC"',
|
||||
'"Sarasa Mono TC"',
|
||||
'"Maple Mono CN"',
|
||||
'"LXGW WenKai Mono"',
|
||||
'"Noto Sans Mono CJK SC"',
|
||||
'"Source Han Mono SC"',
|
||||
'"NSimSun"',
|
||||
'"SimSun"',
|
||||
];
|
||||
|
||||
export const CJK_SYSTEM_FALLBACK_STACK = CJK_SYSTEM_FALLBACK_FONTS.join(', ');
|
||||
|
||||
const NERD_FONT_FALLBACK_FONTS = [
|
||||
'"Symbols Nerd Font Mono"',
|
||||
'"Symbols Nerd Font"',
|
||||
];
|
||||
|
||||
// Per-OS default CJK font when user hasn't explicitly set fallbackFont
|
||||
// AND the current Latin font has no recommended pairing.
|
||||
// All choices are TRUE monospace fonts that keep the terminal grid
|
||||
// aligned. macOS has no system-installed monospace CJK font, so we
|
||||
// reference Sarasa Mono SC which netcatty bundles as a webfont.
|
||||
export function getDefaultCjkFallback(platform: SupportedPlatform): string {
|
||||
if (platform === 'win32') return 'SimSun';
|
||||
if (platform === 'darwin') return 'Sarasa Mono SC';
|
||||
return 'Noto Sans Mono CJK SC';
|
||||
}
|
||||
|
||||
// Every entry must point at a TRUE monospace CJK font. Sarasa Mono SC
|
||||
// is the safest universal choice because netcatty bundles it via
|
||||
// @font-face, so it works even on machines without other CJK monospace
|
||||
// fonts installed.
|
||||
const PER_FONT_CJK_PAIRING: Record<string, string> = {
|
||||
'fira-code': 'Sarasa Mono SC',
|
||||
'fira-mono': 'Sarasa Mono SC',
|
||||
'jetbrains-mono': 'Sarasa Mono SC',
|
||||
'cascadia-code': 'Sarasa Mono SC',
|
||||
'cascadia-mono': 'Sarasa Mono SC',
|
||||
'source-code-pro': 'Source Han Mono SC',
|
||||
'ibm-plex-mono': 'Sarasa Mono SC',
|
||||
'iosevka': 'Sarasa Mono SC',
|
||||
'ioskeley-mono': 'Sarasa Mono SC',
|
||||
'mononoki': 'Sarasa Mono SC',
|
||||
'menlo': 'Sarasa Mono SC',
|
||||
'monaco': 'Sarasa Mono SC',
|
||||
'consolas': 'Sarasa Mono SC',
|
||||
'courier-new': 'Sarasa Mono SC',
|
||||
'dejavu-sans-mono':'Noto Sans Mono CJK SC',
|
||||
'liberation-mono': 'Noto Sans Mono CJK SC',
|
||||
'inconsolata': 'Noto Sans Mono CJK SC',
|
||||
'victor-mono': 'Sarasa Mono SC',
|
||||
'roboto-mono': 'Noto Sans Mono CJK SC',
|
||||
'space-mono': 'Sarasa Mono SC',
|
||||
'hack': 'Sarasa Mono SC',
|
||||
'ubuntu-mono': 'Noto Sans Mono CJK SC',
|
||||
'go-mono': 'Sarasa Mono SC',
|
||||
};
|
||||
|
||||
export function getRecommendedCjkFor(
|
||||
latinFontId: string,
|
||||
platform: SupportedPlatform,
|
||||
): string | null {
|
||||
void platform;
|
||||
return PER_FONT_CJK_PAIRING[latinFontId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a CSS font-family list on commas that are OUTSIDE quoted family
|
||||
* names. CSS permits commas inside quoted family names (e.g.
|
||||
* `"Foo, Inc. Mono"`); a naive `string.split(',')` would tokenize that
|
||||
* into broken pieces like `"Foo` and `Inc. Mono"`. Exported so other
|
||||
* font-parsing call sites (extractPrimaryFamily, etc.) share the same
|
||||
* rules.
|
||||
*/
|
||||
export function splitFontFamilyList(css: string): string[] {
|
||||
const tokens: string[] = [];
|
||||
let buf = '';
|
||||
let quote: '"' | "'" | null = null;
|
||||
for (let i = 0; i < css.length; i++) {
|
||||
const c = css[i];
|
||||
if (quote) {
|
||||
buf += c;
|
||||
if (c === '\\' && i + 1 < css.length) {
|
||||
buf += css[++i];
|
||||
continue;
|
||||
}
|
||||
if (c === quote) quote = null;
|
||||
continue;
|
||||
}
|
||||
if (c === '"' || c === "'") {
|
||||
buf += c;
|
||||
quote = c;
|
||||
continue;
|
||||
}
|
||||
if (c === ',') {
|
||||
const trimmed = buf.trim();
|
||||
if (trimmed) tokens.push(trimmed);
|
||||
buf = '';
|
||||
continue;
|
||||
}
|
||||
buf += c;
|
||||
}
|
||||
const tail = buf.trim();
|
||||
if (tail) tokens.push(tail);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
const CSS_GENERIC_FONT_FAMILIES = new Set([
|
||||
'serif',
|
||||
'sans-serif',
|
||||
'monospace',
|
||||
'cursive',
|
||||
'fantasy',
|
||||
'system-ui',
|
||||
'ui-serif',
|
||||
'ui-sans-serif',
|
||||
'ui-monospace',
|
||||
'ui-rounded',
|
||||
'math',
|
||||
'emoji',
|
||||
'fangsong',
|
||||
]);
|
||||
|
||||
function quoteIfNeeded(family: string): string {
|
||||
const trimmed = family.trim();
|
||||
if (!trimmed) return '';
|
||||
if (CSS_GENERIC_FONT_FAMILIES.has(trimmed.toLowerCase())) return trimmed;
|
||||
if (
|
||||
(trimmed.startsWith('"') && trimmed.endsWith('"'))
|
||||
|| (trimmed.startsWith("'") && trimmed.endsWith("'"))
|
||||
) return trimmed;
|
||||
const escaped = trimmed.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
return `"${escaped}"`;
|
||||
}
|
||||
|
||||
interface ComposeArgs {
|
||||
primaryFamily: string;
|
||||
userFallback: string;
|
||||
latinFontId: string;
|
||||
platform: SupportedPlatform;
|
||||
}
|
||||
|
||||
export function composeFontFamilyStack(args: ComposeArgs): string {
|
||||
const { primaryFamily, userFallback, latinFontId, platform } = args;
|
||||
|
||||
const userFallbackQuoted = userFallback.trim() ? quoteIfNeeded(userFallback) : null;
|
||||
|
||||
const recommended = userFallbackQuoted
|
||||
? null
|
||||
: (getRecommendedCjkFor(latinFontId, platform) ?? getDefaultCjkFallback(platform));
|
||||
const recommendedQuoted = recommended ? quoteIfNeeded(recommended) : null;
|
||||
|
||||
const seen = new Set<string>();
|
||||
const pieces: string[] = [];
|
||||
const push = (item: string | null | undefined) => {
|
||||
if (!item) return;
|
||||
const key = item.toLowerCase();
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
pieces.push(item);
|
||||
};
|
||||
|
||||
// Quote-aware split so a family name like `"Foo, Inc. Mono"` keeps
|
||||
// its comma intact instead of being shredded into `"Foo` / `Inc. Mono"`.
|
||||
for (const p of splitFontFamilyList(primaryFamily)) {
|
||||
if (p.toLowerCase() === 'monospace') continue;
|
||||
push(p);
|
||||
}
|
||||
|
||||
// Latin-only fallback (bundled via @fontsource/jetbrains-mono in
|
||||
// index.tsx). Catches Latin glyphs when the primary font isn't
|
||||
// installed without intercepting CJK glyphs the way the bare
|
||||
// `monospace` generic would on macOS Chrome (where the generic
|
||||
// monospace pulls in PingFang via system CJK fallback, masking the
|
||||
// user's CJK font choice).
|
||||
//
|
||||
// Per-glyph CSS fallback then behaves as intended:
|
||||
// - Latin chars: primary (if installed) → JetBrains Mono. Cells
|
||||
// stay aligned because JetBrains Mono is true monospace.
|
||||
// - CJK chars: primary (no) → JetBrains Mono (no CJK glyphs) →
|
||||
// user-chosen CJK font (or per-Latin-font recommendation) →
|
||||
// system CJK stack.
|
||||
// - Nerd PUA: all of the above (none have PUA) → Nerd Font stack.
|
||||
push('"JetBrains Mono"');
|
||||
|
||||
push(userFallbackQuoted);
|
||||
push(recommendedQuoted);
|
||||
|
||||
for (const sys of CJK_SYSTEM_FALLBACK_FONTS) push(sys);
|
||||
for (const nerd of NERD_FONT_FALLBACK_FONTS) push(nerd);
|
||||
|
||||
// Final safety net only — should rarely be reached because JetBrains
|
||||
// Mono covers Latin and the CJK stack covers Chinese glyphs. Kept
|
||||
// for the edge case where bundled fonts fail to load.
|
||||
push('monospace');
|
||||
|
||||
return pieces.join(', ');
|
||||
}
|
||||
9
infrastructure/config/defaultData.ts
Normal file
9
infrastructure/config/defaultData.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Host, Snippet } from '../../domain/models';
|
||||
|
||||
export const INITIAL_HOSTS: Host[] = [];
|
||||
|
||||
export const INITIAL_SNIPPETS: Snippet[] = [
|
||||
{ id: '1', label: 'Check Disk Space', command: 'df -h', tags: [] },
|
||||
{ id: '2', label: 'Tail System Log', command: 'tail -f /var/log/syslog', tags: [] },
|
||||
{ id: '3', label: 'Update Ubuntu', command: 'sudo apt update && sudo apt upgrade -y', tags: [] },
|
||||
];
|
||||
159
infrastructure/config/fonts.test.ts
Normal file
159
infrastructure/config/fonts.test.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
TERMINAL_FONTS,
|
||||
getDefaultTerminalFontIdForPlatform,
|
||||
detectFontPlatform,
|
||||
resolveTerminalFontFamilyId,
|
||||
isSameResolvedTerminalFont,
|
||||
TERMINAL_FONT_AUTO,
|
||||
} from './fonts';
|
||||
|
||||
/**
|
||||
* Proportional (non-monospace) fonts must never appear in the terminal
|
||||
* primary font dropdown. They produce broken cell-grid alignment because
|
||||
* xterm.js samples cell width from a single probe glyph, and a font with
|
||||
* variable-width Latin glyphs renders other characters with inconsistent
|
||||
* widths around (or beyond) that cell.
|
||||
*/
|
||||
const KNOWN_PROPORTIONAL_FONTS = [
|
||||
// CJK system fonts — proportional sans-serif designed for body text.
|
||||
'PingFang SC',
|
||||
'PingFang TC',
|
||||
'PingFang HK',
|
||||
'Microsoft YaHei',
|
||||
'Microsoft YaHei UI',
|
||||
'Hiragino Sans GB',
|
||||
'Hiragino Sans',
|
||||
'Heiti SC',
|
||||
'Heiti TC',
|
||||
// Latin proportional fonts that get mistakenly listed as "terminal
|
||||
// fonts". Comic Sans MS was historically in this dropdown labeled
|
||||
// "non-traditional terminal font" — picking it produced bloated cell
|
||||
// widths because Comic Sans is a handwriting-style proportional face.
|
||||
'Comic Sans MS',
|
||||
'Arial',
|
||||
'Helvetica',
|
||||
'Times New Roman',
|
||||
'Times',
|
||||
'Georgia',
|
||||
'Verdana',
|
||||
'Trebuchet MS',
|
||||
'Tahoma',
|
||||
];
|
||||
|
||||
describe('TERMINAL_FONTS dropdown contents', () => {
|
||||
it('does not list any known proportional font as a primary choice', () => {
|
||||
for (const banned of KNOWN_PROPORTIONAL_FONTS) {
|
||||
const matches = TERMINAL_FONTS.filter((f) =>
|
||||
f.name === banned ||
|
||||
f.family.includes(`"${banned}"`) ||
|
||||
f.family.split(',')[0].trim() === banned,
|
||||
);
|
||||
assert.deepEqual(
|
||||
matches,
|
||||
[],
|
||||
`${banned} must not appear in TERMINAL_FONTS — it is proportional and breaks terminal grid alignment`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('every entry has a non-empty id, name, and family', () => {
|
||||
for (const font of TERMINAL_FONTS) {
|
||||
assert.ok(font.id.length > 0, `${JSON.stringify(font)} missing id`);
|
||||
assert.ok(font.name.length > 0, `${font.id} missing name`);
|
||||
assert.ok(font.family.length > 0, `${font.id} missing family`);
|
||||
}
|
||||
});
|
||||
|
||||
it('font ids are unique', () => {
|
||||
const seen = new Set<string>();
|
||||
for (const font of TERMINAL_FONTS) {
|
||||
assert.equal(seen.has(font.id), false, `duplicate id: ${font.id}`);
|
||||
seen.add(font.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDefaultTerminalFontIdForPlatform', () => {
|
||||
it('uses a locally-installed Windows font (no webfont swap on cold start)', () => {
|
||||
assert.equal(getDefaultTerminalFontIdForPlatform('win32'), 'consolas');
|
||||
});
|
||||
|
||||
it('uses the most widely pre-installed Linux monospace font', () => {
|
||||
assert.equal(getDefaultTerminalFontIdForPlatform('linux'), 'dejavu-sans-mono');
|
||||
});
|
||||
|
||||
it('keeps the macOS system font default', () => {
|
||||
assert.equal(getDefaultTerminalFontIdForPlatform('darwin'), 'menlo');
|
||||
});
|
||||
|
||||
it('falls back to the macOS default for unknown platforms', () => {
|
||||
assert.equal(getDefaultTerminalFontIdForPlatform('freebsd'), 'menlo');
|
||||
});
|
||||
|
||||
it('only ever returns ids that exist in TERMINAL_FONTS', () => {
|
||||
const ids = new Set(TERMINAL_FONTS.map((f) => f.id));
|
||||
for (const platform of ['darwin', 'win32', 'linux', 'unknown']) {
|
||||
assert.ok(
|
||||
ids.has(getDefaultTerminalFontIdForPlatform(platform)),
|
||||
`default for ${platform} not in TERMINAL_FONTS`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveTerminalFontFamilyId', () => {
|
||||
it('resolves the auto sentinel to the per-platform default', () => {
|
||||
assert.equal(resolveTerminalFontFamilyId(TERMINAL_FONT_AUTO, 'Win32'), 'consolas');
|
||||
assert.equal(resolveTerminalFontFamilyId(TERMINAL_FONT_AUTO, 'MacIntel'), 'menlo');
|
||||
assert.equal(resolveTerminalFontFamilyId(TERMINAL_FONT_AUTO, 'Linux x86_64'), 'dejavu-sans-mono');
|
||||
});
|
||||
|
||||
it('treats empty/nullish ids as auto (resolves per platform)', () => {
|
||||
assert.equal(resolveTerminalFontFamilyId('', 'Win32'), 'consolas');
|
||||
assert.equal(resolveTerminalFontFamilyId(null, 'MacIntel'), 'menlo');
|
||||
assert.equal(resolveTerminalFontFamilyId(undefined, 'Linux'), 'dejavu-sans-mono');
|
||||
});
|
||||
|
||||
it('keeps an explicit font id regardless of platform', () => {
|
||||
assert.equal(resolveTerminalFontFamilyId('jetbrains-mono', 'Win32'), 'jetbrains-mono');
|
||||
assert.equal(resolveTerminalFontFamilyId('menlo', 'Win32'), 'menlo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSameResolvedTerminalFont', () => {
|
||||
it('treats clicking the displayed default while stored=auto as a no-op', () => {
|
||||
// Prevents the side panel from pinning a concrete per-OS font (which would
|
||||
// then sync across devices) on a no-op-looking click of the shown default.
|
||||
assert.equal(isSameResolvedTerminalFont('consolas', TERMINAL_FONT_AUTO, 'Win32'), true);
|
||||
assert.equal(isSameResolvedTerminalFont('menlo', TERMINAL_FONT_AUTO, 'MacIntel'), true);
|
||||
assert.equal(isSameResolvedTerminalFont('dejavu-sans-mono', TERMINAL_FONT_AUTO, 'Linux'), true);
|
||||
});
|
||||
|
||||
it('reports a real change when a different font is selected', () => {
|
||||
assert.equal(isSameResolvedTerminalFont('jetbrains-mono', TERMINAL_FONT_AUTO, 'Win32'), false);
|
||||
});
|
||||
|
||||
it('compares concrete ids directly', () => {
|
||||
assert.equal(isSameResolvedTerminalFont('fira-code', 'fira-code', 'Win32'), true);
|
||||
assert.equal(isSameResolvedTerminalFont('fira-code', 'menlo', 'Win32'), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectFontPlatform', () => {
|
||||
it('maps navigator.platform Windows values to win32', () => {
|
||||
assert.equal(detectFontPlatform('Win32'), 'win32');
|
||||
assert.equal(detectFontPlatform('Windows'), 'win32');
|
||||
});
|
||||
|
||||
it('maps macOS and iOS values to darwin', () => {
|
||||
assert.equal(detectFontPlatform('MacIntel'), 'darwin');
|
||||
assert.equal(detectFontPlatform('iPhone'), 'darwin');
|
||||
});
|
||||
|
||||
it('treats everything else (incl. Linux) as linux', () => {
|
||||
assert.equal(detectFontPlatform('Linux x86_64'), 'linux');
|
||||
assert.equal(detectFontPlatform(''), 'linux');
|
||||
});
|
||||
});
|
||||
178
infrastructure/config/fonts.ts
Normal file
178
infrastructure/config/fonts.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Terminal Fonts Configuration
|
||||
*
|
||||
* `family` is the raw CSS font-family string for the Latin glyphs only.
|
||||
* CJK and icon fallbacks are composed at runtime by composeFontFamilyStack()
|
||||
* in cjkFonts.ts, which lets users pick the CJK font independently or have
|
||||
* one chosen automatically per Latin font.
|
||||
*/
|
||||
|
||||
export interface TerminalFont {
|
||||
id: string;
|
||||
name: string;
|
||||
family: string;
|
||||
description: string;
|
||||
category: 'monospace' | 'proportional';
|
||||
}
|
||||
|
||||
const BASE_TERMINAL_FONTS: TerminalFont[] = [
|
||||
// Existing Latin monospace fonts (ids unchanged for sync compatibility)
|
||||
{ id: 'menlo', name: 'Menlo', family: 'Menlo, monospace', description: 'macOS system font, clean and professional', category: 'monospace' },
|
||||
{ id: 'monaco', name: 'Monaco', family: 'Monaco, monospace', description: 'Classic monospace, excellent readability', category: 'monospace' },
|
||||
{ id: 'consolas', name: 'Consolas', family: 'Consolas, monospace', description: 'Windows-style monospace, clear and compact', category: 'monospace' },
|
||||
{ id: 'courier-new', name: 'Courier New', family: '"Courier New", monospace', description: 'Classic typewriter style, universal support', category: 'monospace' },
|
||||
{ id: 'source-code-pro', name: 'Source Code Pro', family: '"Source Code Pro", monospace', description: "Adobe's professional programming font", category: 'monospace' },
|
||||
{ id: 'fira-code', name: 'Fira Code', family: '"Fira Code", monospace', description: 'Monospace font with programming ligatures', category: 'monospace' },
|
||||
{ id: 'fira-mono', name: 'Fira Mono', family: '"Fira Mono", monospace', description: 'Clean monospace without ligatures', category: 'monospace' },
|
||||
{ id: 'inconsolata', name: 'Inconsolata', family: 'Inconsolata, monospace', description: 'Elegant and readable monospace font', category: 'monospace' },
|
||||
{ id: 'dejavu-sans-mono', name: 'DejaVu Sans Mono', family: '"DejaVu Sans Mono", monospace', description: 'Wide character support, very readable', category: 'monospace' },
|
||||
{ id: 'liberation-mono', name: 'Liberation Mono', family: '"Liberation Mono", monospace', description: 'Open source monospace font, Courier alternative', category: 'monospace' },
|
||||
{ id: 'jetbrains-mono', name: 'JetBrains Mono', family: '"JetBrains Mono", monospace', description: 'Professional font designed for IDEs', category: 'monospace' },
|
||||
{ id: 'victor-mono', name: 'Victor Mono', family: '"Victor Mono", monospace', description: 'Stylish monospace with italic support', category: 'monospace' },
|
||||
{ id: 'cascadia-code', name: 'Cascadia Code', family: '"Cascadia Code", monospace', description: "Microsoft's modern monospace font", category: 'monospace' },
|
||||
{ id: 'cascadia-mono', name: 'Cascadia Mono', family: '"Cascadia Mono", monospace', description: 'Cascadia without ligatures', category: 'monospace' },
|
||||
{ id: 'droid-sans-mono', name: 'Droid Sans Mono', family: '"Droid Sans Mono", monospace', description: "Google's Droid monospace font", category: 'monospace' },
|
||||
{ id: 'ubuntu-mono', name: 'Ubuntu Mono', family: '"Ubuntu Mono", monospace', description: "Ubuntu's official monospace font", category: 'monospace' },
|
||||
{ id: 'roboto-mono', name: 'Roboto Mono', family: '"Roboto Mono", monospace', description: "Google's Roboto monospace variant", category: 'monospace' },
|
||||
{ id: 'ibm-plex-mono', name: 'IBM Plex Mono', family: '"IBM Plex Mono", monospace', description: "IBM's professional monospace font", category: 'monospace' },
|
||||
{ id: 'space-mono', name: 'Space Mono', family: '"Space Mono", monospace', description: 'Geometric monospace with strong personality', category: 'monospace' },
|
||||
{ id: 'input-mono', name: 'Input Mono', family: '"Input Mono", monospace', description: 'Designed specifically for coding', category: 'monospace' },
|
||||
{ id: 'hack', name: 'Hack', family: 'Hack, monospace', description: 'Designed for source code, excellent in terminals', category: 'monospace' },
|
||||
{ id: 'anonymous-pro', name: 'Anonymous Pro', family: '"Anonymous Pro", monospace', description: 'Designed for coding and terminal use', category: 'monospace' },
|
||||
{ id: 'programmer-fonts', name: 'Programmer Fonts', family: '"Programmer Fonts", monospace', description: 'Optimized for programming with clear glyphs', category: 'monospace' },
|
||||
{ id: 'pt-mono', name: 'PT Mono', family: '"PT Mono", monospace', description: "ParaType's monospace font", category: 'monospace' },
|
||||
{ id: 'iosevka', name: 'Iosevka', family: 'Iosevka, monospace', description: 'Highly customizable monospace font', category: 'monospace' },
|
||||
{ id: 'ioskeley-mono', name: 'Ioskeley Mono', family: '"Ioskeley Mono", monospace', description: 'Iosevka variant mimicking Berkeley Mono style', category: 'monospace' },
|
||||
{ id: 'mononoki', name: 'Mononoki', family: 'Mononoki, monospace', description: 'Crisp and clear monospace with ligatures', category: 'monospace' },
|
||||
{ id: 'go-mono', name: 'Go Mono', family: '"Go Mono", monospace', description: "Google Go's monospace font", category: 'monospace' },
|
||||
{ id: 'overpass-mono', name: 'Overpass Mono', family: '"Overpass Mono", monospace', description: 'Open source monospace with good coverage', category: 'monospace' },
|
||||
|
||||
// True monospace CJK-coverage fonts only. PingFang SC and Microsoft
|
||||
// YaHei UI (the OS system fonts) are deliberately omitted — they are
|
||||
// proportional sans-serif designs whose Latin glyphs render with
|
||||
// variable widths and whose CJK glyphs don't fit a terminal's 2x cell
|
||||
// grid. Picking one as the primary font produced visibly bloated
|
||||
// spacing for ASCII characters in #931.
|
||||
{ id: 'sarasa-mono-sc', name: 'Sarasa Mono SC', family: '"Sarasa Mono SC", monospace', description: 'Iosevka + Source Han Sans (Simplified Chinese), 2:1 monospace', category: 'monospace' },
|
||||
{ id: 'sarasa-mono-tc', name: 'Sarasa Mono TC', family: '"Sarasa Mono TC", monospace', description: 'Iosevka + Source Han Sans (Traditional Chinese), 2:1 monospace', category: 'monospace' },
|
||||
{ id: 'maple-mono-cn', name: 'Maple Mono CN', family: '"Maple Mono CN", monospace', description: 'Maple Mono with unified Latin + Simplified Chinese metrics', category: 'monospace' },
|
||||
{ id: 'lxgw-wenkai-mono', name: 'LXGW WenKai Mono', family: '"LXGW WenKai Mono", monospace', description: 'Monospace Kaishu (regular-script) derived from Fontworks Klee One', category: 'monospace' },
|
||||
];
|
||||
|
||||
export const TERMINAL_FONTS: TerminalFont[] = BASE_TERMINAL_FONTS;
|
||||
|
||||
export const DEFAULT_FONT_SIZE = 14;
|
||||
export const MIN_FONT_SIZE = 10;
|
||||
export const MAX_FONT_SIZE = 32;
|
||||
|
||||
export type FontPlatform = 'darwin' | 'win32' | 'linux' | (string & {});
|
||||
|
||||
/**
|
||||
* The default terminal font id for a given OS. Chosen so the primary
|
||||
* Latin glyphs render with a font that ships with the OS — avoiding the
|
||||
* cold-start cell-misalignment in #1647, where a bundled webfont
|
||||
* (JetBrains Mono, font-display: swap) swapped in *after* xterm measured
|
||||
* the cell grid and left ASCII garbled until a manual window resize.
|
||||
*
|
||||
* - darwin: Menlo is a macOS system font (instant, no swap).
|
||||
* - win32: Consolas ships with every Windows since Vista.
|
||||
* - linux: DejaVu Sans Mono is the most widely pre-installed Linux
|
||||
* monospace; if a given distro lacks it, the runtime font-ready
|
||||
* remeasure (useTerminalEffects) still recovers any swap.
|
||||
*
|
||||
* Unknown platforms fall back to the macOS default.
|
||||
*/
|
||||
export function getDefaultTerminalFontIdForPlatform(platform: FontPlatform): string {
|
||||
if (platform === 'win32') return 'consolas';
|
||||
if (platform === 'linux') return 'dejavu-sans-mono';
|
||||
return 'menlo';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `navigator.platform` string to the coarse OS buckets used
|
||||
* by getDefaultTerminalFontIdForPlatform. Mirrors the detection already
|
||||
* used for the CJK fallback stack in Terminal.tsx (Mac/Win regex on
|
||||
* navigator.platform); anything else — including Linux — is `linux`.
|
||||
*/
|
||||
export function detectFontPlatform(navigatorPlatform: string): FontPlatform {
|
||||
if (/Win/i.test(navigatorPlatform)) return 'win32';
|
||||
if (/Mac|iPhone|iPad|iPod/i.test(navigatorPlatform)) return 'darwin';
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
/**
|
||||
* The stored sentinel meaning "follow the OS default font". It is
|
||||
* persisted and cloud-synced as-is (platform-neutral), so each device
|
||||
* resolves it to its own locally-installed default at render time —
|
||||
* unlike a concrete platform id, which would leak one OS's font to
|
||||
* another via sync and reintroduce #1647 on the receiving machine.
|
||||
*/
|
||||
export const TERMINAL_FONT_AUTO = 'auto';
|
||||
|
||||
/**
|
||||
* Resolve a stored terminal font id to a concrete font id. The `auto`
|
||||
* sentinel (and any empty/nullish value) resolves to the per-platform
|
||||
* default; an explicit user choice is returned unchanged.
|
||||
*/
|
||||
export function resolveTerminalFontFamilyId(
|
||||
id: string | null | undefined,
|
||||
navigatorPlatform: string,
|
||||
): string {
|
||||
if (id && id !== TERMINAL_FONT_AUTO) return id;
|
||||
return getDefaultTerminalFontIdForPlatform(detectFontPlatform(navigatorPlatform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether two terminal font ids resolve to the same concrete font on this
|
||||
* platform. Font pickers display the resolved concrete font for an `auto`
|
||||
* value, so a click on the shown default arrives as that concrete id while
|
||||
* the stored value is still `auto`; comparing raw would treat it as a
|
||||
* change and pin a per-OS font (which then syncs across devices). Comparing
|
||||
* resolved ids makes that click a correct no-op.
|
||||
*/
|
||||
export function isSameResolvedTerminalFont(
|
||||
a: string | null | undefined,
|
||||
b: string | null | undefined,
|
||||
navigatorPlatform: string,
|
||||
): boolean {
|
||||
return (
|
||||
resolveTerminalFontFamilyId(a, navigatorPlatform) ===
|
||||
resolveTerminalFontFamilyId(b, navigatorPlatform)
|
||||
);
|
||||
}
|
||||
|
||||
// Font ids that earlier versions of netcatty exposed in the primary font
|
||||
// dropdown but that are proportional (non-monospace) and produce broken
|
||||
// cell-grid alignment when used as a terminal font. Reads should migrate
|
||||
// these to a sane default.
|
||||
const DEPRECATED_PRIMARY_FONT_IDS = new Set<string>([
|
||||
'pingfang-sc',
|
||||
'microsoft-yahei',
|
||||
'comic-sans-ms',
|
||||
]);
|
||||
|
||||
export function isDeprecatedPrimaryFontId(fontId: string | null | undefined): boolean {
|
||||
return !!fontId && DEPRECATED_PRIMARY_FONT_IDS.has(fontId);
|
||||
}
|
||||
|
||||
/**
|
||||
* In-place migration for any object carrying `fontFamily` /
|
||||
* `fontFamilyOverride` (Host, GroupConfig). When the saved id is one
|
||||
* we've since removed from TERMINAL_FONTS, drop the override so the
|
||||
* record inherits the global default rather than silently rendering
|
||||
* "fallback to fonts[0]" while still claiming an override is active.
|
||||
*
|
||||
* Returns the (possibly new) value to assign back. Caller decides
|
||||
* whether to mutate or copy; both are safe with this shape.
|
||||
*/
|
||||
export function migrateDeprecatedFontOverride<
|
||||
T extends { fontFamily?: string; fontFamilyOverride?: boolean },
|
||||
>(record: T): T {
|
||||
if (!isDeprecatedPrimaryFontId(record.fontFamily)) return record;
|
||||
const next = { ...record };
|
||||
delete next.fontFamily;
|
||||
if (next.fontFamilyOverride === true) {
|
||||
next.fontFamilyOverride = false;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
30
infrastructure/config/i18n.ts
Normal file
30
infrastructure/config/i18n.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export type LocaleOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export const DEFAULT_UI_LOCALE = 'en';
|
||||
|
||||
// Add new languages by appending to this list and providing message dictionaries.
|
||||
export const SUPPORTED_UI_LOCALES: LocaleOption[] = [
|
||||
{ id: 'en', label: 'English' },
|
||||
{ id: 'es', label: 'Español' },
|
||||
{ id: 'ru', label: 'Русский' },
|
||||
{ id: 'zh-CN', label: '简体中文' },
|
||||
{ id: 'zh-TW', label: '繁體中文' },
|
||||
];
|
||||
|
||||
const isSupportedLocale = (locale: string): boolean => {
|
||||
return SUPPORTED_UI_LOCALES.some((l) => l.id === locale);
|
||||
};
|
||||
|
||||
export const resolveSupportedLocale = (locale: string): string => {
|
||||
if (isSupportedLocale(locale)) return locale;
|
||||
const base = locale.split('-')[0] || locale;
|
||||
const baseExact = SUPPORTED_UI_LOCALES.find((l) => l.id === base)?.id;
|
||||
if (baseExact) return baseExact;
|
||||
const basePrefix = SUPPORTED_UI_LOCALES.find((l) => l.id.startsWith(`${base}-`))?.id;
|
||||
if (basePrefix) return basePrefix;
|
||||
return DEFAULT_UI_LOCALE;
|
||||
};
|
||||
|
||||
18
infrastructure/config/sshTransportIdleTtl.test.ts
Normal file
18
infrastructure/config/sshTransportIdleTtl.test.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
DEFAULT_SSH_TRANSPORT_IDLE_TTL_MS,
|
||||
resolveSshTransportIdleTtlMs,
|
||||
} from "./sshTransportIdleTtl.ts";
|
||||
|
||||
test("ssh transport idle TTL defaults to 5 minutes", () => {
|
||||
assert.equal(DEFAULT_SSH_TRANSPORT_IDLE_TTL_MS, 5 * 60_000);
|
||||
assert.equal(resolveSshTransportIdleTtlMs(() => null), DEFAULT_SSH_TRANSPORT_IDLE_TTL_MS);
|
||||
});
|
||||
|
||||
test("ssh transport idle TTL accepts presets including never-reclaim", () => {
|
||||
assert.equal(resolveSshTransportIdleTtlMs(() => 60_000), 60_000);
|
||||
assert.equal(resolveSshTransportIdleTtlMs(() => 0), 0);
|
||||
assert.equal(resolveSshTransportIdleTtlMs(() => 123), DEFAULT_SSH_TRANSPORT_IDLE_TTL_MS);
|
||||
});
|
||||
33
infrastructure/config/sshTransportIdleTtl.ts
Normal file
33
infrastructure/config/sshTransportIdleTtl.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Idle park TTL for the main-process SSH transport registry
|
||||
* (shell / SFTP / transfer / port-forward shared connections).
|
||||
* 0 keeps transports warm until app quit or explicit discard (never auto-reclaim).
|
||||
* Positive values park for that many ms after the last lease returns.
|
||||
*/
|
||||
|
||||
export const DEFAULT_SSH_TRANSPORT_IDLE_TTL_MS = 5 * 60_000;
|
||||
|
||||
export const SSH_TRANSPORT_IDLE_TTL_PRESETS_MS = [
|
||||
60_000,
|
||||
5 * 60_000,
|
||||
15 * 60_000,
|
||||
30 * 60_000,
|
||||
0,
|
||||
] as const;
|
||||
|
||||
export type SshTransportIdleTtlMs = (typeof SSH_TRANSPORT_IDLE_TTL_PRESETS_MS)[number];
|
||||
|
||||
export function resolveSshTransportIdleTtlMs(
|
||||
readStoredValue: () => number | null | undefined,
|
||||
): number {
|
||||
const stored = readStoredValue();
|
||||
if (stored === 0) return 0;
|
||||
if (
|
||||
typeof stored === "number"
|
||||
&& Number.isFinite(stored)
|
||||
&& (SSH_TRANSPORT_IDLE_TTL_PRESETS_MS as readonly number[]).includes(stored)
|
||||
) {
|
||||
return stored;
|
||||
}
|
||||
return DEFAULT_SSH_TRANSPORT_IDLE_TTL_MS;
|
||||
}
|
||||
298
infrastructure/config/storageKeys.ts
Normal file
298
infrastructure/config/storageKeys.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
export const STORAGE_KEY_HOSTS = 'netcatty_hosts_v1';
|
||||
export const STORAGE_KEY_KEYS = 'netcatty_keys_v1';
|
||||
export const STORAGE_KEY_GROUPS = 'netcatty_groups_v1';
|
||||
export const STORAGE_KEY_CUSTOM_GROUPS = STORAGE_KEY_GROUPS;
|
||||
export const STORAGE_KEY_SNIPPETS = 'netcatty_snippets_v1';
|
||||
export const STORAGE_KEY_SNIPPET_PACKAGES = 'netcatty_snippet_packages_v1';
|
||||
export const STORAGE_KEY_NOTES = 'netcatty_notes_v1';
|
||||
export const STORAGE_KEY_NOTE_GROUPS = 'netcatty_note_groups_v1';
|
||||
/** Last-filled values per snippet id for {{variable}} placeholders. */
|
||||
export const STORAGE_KEY_SNIPPET_VAR_VALUES = 'netcatty_snippet_var_values_v1';
|
||||
export const STORAGE_KEY_THEME = 'netcatty_theme_v1';
|
||||
export const STORAGE_KEY_COLOR = 'netcatty_color_v1';
|
||||
export const STORAGE_KEY_ACCENT_MODE = 'netcatty_accent_mode_v1';
|
||||
export const STORAGE_KEY_UI_THEME_LIGHT = 'netcatty_ui_theme_light_v1';
|
||||
export const STORAGE_KEY_UI_THEME_DARK = 'netcatty_ui_theme_dark_v1';
|
||||
export const STORAGE_KEY_UI_FONT_FAMILY = 'netcatty_ui_font_family_v1';
|
||||
export const STORAGE_KEY_SYNC = 'netcatty_sync_v1';
|
||||
/** Device-local experimental convergent-sync toggle; never included in SyncPayload settings. */
|
||||
export const STORAGE_KEY_CONVERGENT_SYNC_CONFIG = 'netcatty_convergent_sync_config_v2';
|
||||
export const STORAGE_KEY_TERM_THEME = 'netcatty_term_theme_v1';
|
||||
export const STORAGE_KEY_TERM_FOLLOW_APP_THEME = 'netcatty_term_follow_app_theme_v1';
|
||||
export const STORAGE_KEY_TERM_THEME_DARK = 'netcatty_term_theme_dark_v1';
|
||||
export const STORAGE_KEY_TERM_THEME_LIGHT = 'netcatty_term_theme_light_v1';
|
||||
export const STORAGE_KEY_TERM_FONT_FAMILY = 'netcatty_term_font_family_v1';
|
||||
export const STORAGE_KEY_TERM_FONT_SIZE = 'netcatty_term_font_size_v1';
|
||||
export const STORAGE_KEY_TERM_SETTINGS = 'netcatty_term_settings_v1';
|
||||
export const STORAGE_KEY_HOTKEY_SCHEME = 'netcatty_hotkey_scheme_v1';
|
||||
export const STORAGE_KEY_CUSTOM_KEY_BINDINGS = 'netcatty_custom_key_bindings_v1';
|
||||
export const STORAGE_KEY_HOTKEY_RECORDING = 'netcatty_hotkey_recording_v1';
|
||||
export const STORAGE_KEY_CUSTOM_CSS = 'netcatty_custom_css_v1';
|
||||
export const STORAGE_KEY_UI_LANGUAGE = 'netcatty_ui_language_v1';
|
||||
export const STORAGE_KEY_PORT_FORWARDING = 'netcatty_port_forwarding_v1';
|
||||
/** Width (px) shared by port forwarding edit, wizard, and host picker panels. */
|
||||
export const STORAGE_KEY_PORT_FORWARDING_PANEL_WIDTH = 'netcatty_port_forwarding_panel_width_v1';
|
||||
export const STORAGE_KEY_PF_PREFER_FORM_MODE = 'netcatty_pf_prefer_form_mode_v1';
|
||||
export const STORAGE_KEY_PF_VIEW_MODE = 'netcatty_pf_view_mode_v1';
|
||||
export const STORAGE_KEY_KNOWN_HOSTS = 'netcatty_known_hosts_v1';
|
||||
export const STORAGE_KEY_SHELL_HISTORY = 'netcatty_shell_history_v1';
|
||||
export const STORAGE_KEY_CONNECTION_LOGS = 'netcatty_connection_logs_v1';
|
||||
/** Side store for unsaved connection-log terminal replay buffers (main blob omits them for perf). */
|
||||
export const STORAGE_KEY_CONNECTION_LOG_TERMINAL_DATA = 'netcatty_connection_log_terminal_data_v1';
|
||||
export const STORAGE_KEY_SESSION_RESTORE = 'netcatty_session_restore_v1';
|
||||
export const STORAGE_KEY_RESTORE_PREVIOUS_SESSION = 'netcatty_restore_previous_session_v1';
|
||||
export const STORAGE_KEY_RESTORE_TERMINAL_CWD = 'netcatty_restore_terminal_cwd_v1';
|
||||
/** Cold-start landing: vault (home) or local terminal when nothing is restored. */
|
||||
export const STORAGE_KEY_STARTUP_LANDING = 'netcatty_startup_landing_v1';
|
||||
export const STORAGE_KEY_IDENTITIES = 'netcatty_identities_v1';
|
||||
export const STORAGE_KEY_PROXY_PROFILES = 'netcatty_proxy_profiles_v1';
|
||||
export const STORAGE_KEY_VAULT_HOSTS_VIEW_MODE = 'netcatty_vault_hosts_view_mode_v1';
|
||||
export const STORAGE_KEY_VAULT_HOSTS_SORT_MODE = 'netcatty_vault_hosts_sort_mode_v1';
|
||||
export const STORAGE_KEY_VAULT_HOSTS_TREE_EXPANDED = 'netcatty_vault_hosts_tree_expanded_v1';
|
||||
export const STORAGE_KEY_VAULT_SIDEBAR_COLLAPSED = 'netcatty_vault_sidebar_collapsed_v1';
|
||||
export const STORAGE_KEY_VAULT_SIDEBAR_WIDTH = 'netcatty_vault_sidebar_width_v1';
|
||||
export const STORAGE_KEY_VAULT_KEYS_VIEW_MODE = 'netcatty_vault_keys_view_mode_v1';
|
||||
export const STORAGE_KEY_VAULT_PROXY_PROFILES_VIEW_MODE = 'netcatty_vault_proxy_profiles_view_mode_v1';
|
||||
export const STORAGE_KEY_VAULT_SNIPPETS_VIEW_MODE = 'netcatty_vault_snippets_view_mode_v1';
|
||||
export const STORAGE_KEY_VAULT_NOTES_VIEW_MODE = 'netcatty_vault_notes_view_mode_v1';
|
||||
export const STORAGE_KEY_VAULT_NOTES_EDITOR_MODE = 'netcatty_vault_notes_editor_mode_v1';
|
||||
export const STORAGE_KEY_VAULT_NOTES_SELECTED_GROUP = 'netcatty_vault_notes_selected_group_v1';
|
||||
export const STORAGE_KEY_VAULT_NOTES_TREE_WIDTH = 'netcatty_vault_notes_tree_width_v1';
|
||||
export const STORAGE_KEY_VAULT_NOTES_FONT_FAMILY = 'netcatty_vault_notes_font_family_v1';
|
||||
export const STORAGE_KEY_VAULT_NOTES_FONT_SIZE = 'netcatty_vault_notes_font_size_v1';
|
||||
export const STORAGE_KEY_VAULT_NOTES_CODE_FONT_SIZE = 'netcatty_vault_notes_code_font_size_v1';
|
||||
/** Inline snippet/script edit panel width (px). */
|
||||
export const STORAGE_KEY_SNIPPETS_PANEL_WIDTH = 'netcatty_snippets_panel_width_v1';
|
||||
/** Inline vault host/group details panel width (px). */
|
||||
export const STORAGE_KEY_VAULT_HOST_PANEL_WIDTH = 'netcatty_vault_host_panel_width_v1';
|
||||
/** Inline snippet script editor height (px) in vault edit panel. */
|
||||
export const STORAGE_KEY_SNIPPET_SCRIPT_EDITOR_HEIGHT = 'netcatty_snippet_script_editor_height_v1';
|
||||
/** Automation script Monaco editor height (px) in vault sidebar. */
|
||||
export const STORAGE_KEY_SCRIPT_EDITOR_HEIGHT = 'netcatty_script_editor_height_v1';
|
||||
/** Terminal compose bar total height (px). */
|
||||
export const STORAGE_KEY_COMPOSE_BAR_HEIGHT = 'netcatty_compose_bar_height_v1';
|
||||
/** Snippet IDs pinned to the terminal compose bar quick strip. */
|
||||
export const STORAGE_KEY_COMPOSE_BAR_PINNED_SNIPPETS = 'netcatty_compose_bar_pinned_snippets_v1';
|
||||
export const STORAGE_KEY_VAULT_KNOWN_HOSTS_VIEW_MODE = 'netcatty_vault_known_hosts_view_mode_v1';
|
||||
/** Device-local: silently import system OpenSSH known_hosts on Vault load (default true). */
|
||||
export const STORAGE_KEY_AUTO_IMPORT_SYSTEM_KNOWN_HOSTS = 'netcatty_auto_import_system_known_hosts_v1';
|
||||
|
||||
// Update check
|
||||
export const STORAGE_KEY_UPDATE_LAST_CHECK = 'netcatty_update_last_check_v1';
|
||||
export const STORAGE_KEY_UPDATE_DISMISSED_VERSION = 'netcatty_update_dismissed_version_v1';
|
||||
export const STORAGE_KEY_UPDATE_LATEST_RELEASE = 'netcatty_update_latest_release_v1';
|
||||
export const STORAGE_KEY_AUTO_UPDATE_ENABLED = 'netcatty_auto_update_enabled_v1';
|
||||
export const STORAGE_KEY_LOCAL_VAULT_BACKUP_MAX_COUNT = 'netcatty_local_vault_backup_max_count_v1';
|
||||
export const STORAGE_KEY_LOCAL_VAULT_BACKUP_LAST_APP_VERSION = 'netcatty_local_vault_backup_last_app_version_v1';
|
||||
|
||||
/**
|
||||
* Cross-window barrier: set while a local vault restore is applying so
|
||||
* auto-sync in another window doesn't upload a pre-restore snapshot
|
||||
* concurrently. The value is an epoch-ms deadline — auto-sync treats any
|
||||
* value in the future as "restore in progress" and any value in the past
|
||||
* as a stale lock that can be ignored. See useAutoSync and
|
||||
* CloudSyncSettings for readers/writers.
|
||||
*/
|
||||
export const STORAGE_KEY_VAULT_RESTORE_IN_PROGRESS_UNTIL = 'netcatty_vault_restore_in_progress_until_v1';
|
||||
|
||||
/**
|
||||
* Apply-in-progress sentinel. Set before a destructive applySyncPayload
|
||||
* starts writing and cleared after it completes successfully. If this
|
||||
* value is present on a later startup, the previous apply was
|
||||
* interrupted mid-way (renderer crash, power loss, IPC failure) and the
|
||||
* local vault is a partial mix of pre-apply and post-apply state.
|
||||
* Auto-sync must refuse to push in that window — otherwise the partial
|
||||
* state would silently overwrite an intact cloud copy — until the user
|
||||
* manually restores from a protective backup or completes a full merge.
|
||||
* The value is a JSON-encoded record (startedAt, protectiveBackupId,
|
||||
* source) so the UI can surface a specific recovery hint rather than a
|
||||
* generic "something broke" warning.
|
||||
*/
|
||||
export const STORAGE_KEY_VAULT_APPLY_IN_PROGRESS = 'netcatty_vault_apply_in_progress_v1';
|
||||
|
||||
// SFTP File Opener Associations
|
||||
export const STORAGE_KEY_SFTP_FILE_ASSOCIATIONS = 'netcatty_sftp_file_associations_v1';
|
||||
export const STORAGE_KEY_SFTP_DEFAULT_OPENER = 'netcatty_sftp_default_opener_v1';
|
||||
|
||||
// SFTP Local Bookmarks
|
||||
export const STORAGE_KEY_SFTP_LOCAL_BOOKMARKS = 'netcatty_sftp_local_bookmarks_v1';
|
||||
|
||||
// SFTP Global Bookmarks (shared across all hosts)
|
||||
export const STORAGE_KEY_SFTP_GLOBAL_BOOKMARKS = 'netcatty_sftp_global_bookmarks_v1';
|
||||
|
||||
// SFTP Settings
|
||||
export const STORAGE_KEY_SFTP_DOUBLE_CLICK_BEHAVIOR = 'netcatty_sftp_double_click_behavior_v1';
|
||||
export const STORAGE_KEY_SFTP_AUTO_SYNC = 'netcatty_sftp_auto_sync_v1';
|
||||
export const STORAGE_KEY_SFTP_SHOW_HIDDEN_FILES = 'netcatty_sftp_show_hidden_files_v1';
|
||||
export const STORAGE_KEY_SFTP_USE_COMPRESSED_UPLOAD = 'netcatty_sftp_use_compressed_upload_v1';
|
||||
export const STORAGE_KEY_SFTP_TRANSFER_CENTER = 'netcatty_sftp_transfer_center_v1';
|
||||
export const STORAGE_KEY_SFTP_AUTO_OPEN_SIDEBAR = 'netcatty_sftp_auto_open_sidebar_v1';
|
||||
export const STORAGE_KEY_SFTP_FOLLOW_TERMINAL_CWD = 'netcatty_sftp_follow_terminal_cwd_v1';
|
||||
export const STORAGE_KEY_SFTP_DEFAULT_VIEW_MODE = 'netcatty_sftp_default_view_mode_v1';
|
||||
/** Compact vs comfortable SFTP file-list row density. */
|
||||
export const STORAGE_KEY_SFTP_LIST_DENSITY = 'netcatty_sftp_list_density_v1';
|
||||
export const STORAGE_KEY_SFTP_HOST_VIEW_MODES = 'netcatty_sftp_host_view_modes_v1';
|
||||
export const STORAGE_KEY_SFTP_VISIBLE_COLUMNS = 'netcatty_sftp_visible_columns_v1';
|
||||
export const STORAGE_KEY_SFTP_DIRECTORIES_FIRST = 'netcatty_sftp_directories_first_v1';
|
||||
/** Dense SFTP toolbar actions: show / collapse / hide + order. */
|
||||
export const STORAGE_KEY_SFTP_TOOLBAR_LAYOUT = 'netcatty_sftp_toolbar_layout_v1';
|
||||
/** Dense terminal session toolbar actions: show / collapse / hide + order. */
|
||||
export const STORAGE_KEY_TERMINAL_TOOLBAR_LAYOUT = 'netcatty_terminal_toolbar_layout_v1';
|
||||
/** Terminal host-tree sidebar toolbar: show / collapse / hide + order. */
|
||||
export const STORAGE_KEY_TERMINAL_HOST_TREE_TOOLBAR_LAYOUT =
|
||||
'netcatty_terminal_host_tree_toolbar_layout_v1';
|
||||
/** Side-panel tab strip: show / collapse / hide + order (supersedes order-only key when present). */
|
||||
export const STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_LAYOUT = 'netcatty_terminal_side_panel_tab_layout_v1';
|
||||
/** System Manager sub-tabs (Overview / Processes / …): show / collapse / hide + order. */
|
||||
export const STORAGE_KEY_SYSTEM_MANAGER_TAB_LAYOUT = 'netcatty_system_manager_tab_layout_v1';
|
||||
export const STORAGE_KEY_SFTP_TRANSFER_PANEL_HEIGHT = 'netcatty_sftp_transfer_panel_height_v1';
|
||||
export const STORAGE_KEY_SFTP_TRANSFER_CHILD_NAME_WIDTH = 'netcatty_sftp_transfer_child_name_width_v1';
|
||||
|
||||
// Editor Settings
|
||||
export const STORAGE_KEY_EDITOR_WORD_WRAP = 'netcatty_editor_word_wrap_v1';
|
||||
|
||||
// Session Logs Settings
|
||||
export const STORAGE_KEY_SESSION_LOGS_ENABLED = 'netcatty_session_logs_enabled_v1';
|
||||
export const STORAGE_KEY_SESSION_LOGS_DIR = 'netcatty_session_logs_dir_v1';
|
||||
export const STORAGE_KEY_SESSION_LOGS_FORMAT = 'netcatty_session_logs_format_v1';
|
||||
export const STORAGE_KEY_SESSION_LOGS_TIMESTAMPS_ENABLED = 'netcatty_session_logs_timestamps_enabled_v1';
|
||||
export const STORAGE_KEY_SSH_DEBUG_LOGS_ENABLED = 'netcatty_ssh_debug_logs_enabled_v1';
|
||||
export const STORAGE_KEY_SSH_DEEP_LINK_ENABLED = 'netcatty_ssh_deep_link_enabled_v1';
|
||||
export const STORAGE_KEY_JMS_DEEP_LINK_ENABLED = 'netcatty_jms_deep_link_enabled_v1';
|
||||
/** Windows Explorer "Open in Netcatty" folder context menu (device-local). */
|
||||
export const STORAGE_KEY_EXPLORER_CONTEXT_MENU_ENABLED = 'netcatty_explorer_context_menu_enabled_v1';
|
||||
|
||||
// Archived legacy key records that are no longer supported by the app (e.g. biometric/WebAuthn/FIDO2 experiments).
|
||||
export const STORAGE_KEY_LEGACY_KEYS = 'netcatty_legacy_keys_v1';
|
||||
|
||||
// Managed Sources - external files that manage groups of hosts (e.g., ~/.ssh/config)
|
||||
export const STORAGE_KEY_MANAGED_SOURCES = 'netcatty_managed_sources_v1';
|
||||
|
||||
// Global Toggle Window Settings (Quake Mode)
|
||||
export const STORAGE_KEY_TOGGLE_WINDOW_HOTKEY = 'netcatty_toggle_window_hotkey_v1';
|
||||
export const STORAGE_KEY_CLOSE_TO_TRAY = 'netcatty_close_to_tray_v1';
|
||||
/** Optimistic cache only — the source of truth is app.getLoginItemSettings() in the main process. */
|
||||
export const STORAGE_KEY_AUTO_LAUNCH_ENABLED = 'netcatty_auto_launch_enabled_v1';
|
||||
/** App-level HTTP(S) proxy for cloud sync / AI (not SSH ProxyJump). */
|
||||
export const STORAGE_KEY_HTTP_NETWORK_PROXY = 'netcatty_http_network_proxy_v1';
|
||||
export const STORAGE_KEY_GLOBAL_HOTKEY_ENABLED = 'netcatty_global_hotkey_enabled_v1';
|
||||
export const STORAGE_KEY_WINDOW_OPACITY = 'netcatty_window_opacity_v1';
|
||||
export const STORAGE_KEY_APP_ICON_VARIANT = 'netcatty_app_icon_variant_v1';
|
||||
// Custom Terminal Themes
|
||||
export const STORAGE_KEY_CUSTOM_THEMES = 'netcatty_custom_themes_v1';
|
||||
|
||||
// AI Settings
|
||||
export const STORAGE_KEY_AI_PROVIDERS = 'netcatty_ai_providers_v1';
|
||||
export const STORAGE_KEY_AI_ACTIVE_PROVIDER = 'netcatty_ai_active_provider_v1';
|
||||
export const STORAGE_KEY_AI_ACTIVE_MODEL = 'netcatty_ai_active_model_v1';
|
||||
export const STORAGE_KEY_AI_PERMISSION_MODE = 'netcatty_ai_permission_mode_v1';
|
||||
export const STORAGE_KEY_AI_TOOL_INTEGRATION_MODE = 'netcatty_ai_tool_integration_mode_v1';
|
||||
export const STORAGE_KEY_AI_HOST_PERMISSIONS = 'netcatty_ai_host_permissions_v1';
|
||||
export const STORAGE_KEY_AI_EXTERNAL_AGENTS = 'netcatty_ai_external_agents_v1';
|
||||
export const STORAGE_KEY_AI_DEFAULT_AGENT = 'netcatty_ai_default_agent_v1';
|
||||
export const STORAGE_KEY_AI_COMMAND_BLOCKLIST = 'netcatty_ai_command_blocklist_v1';
|
||||
export const STORAGE_KEY_AI_COMMAND_TIMEOUT = 'netcatty_ai_command_timeout_v1';
|
||||
export const STORAGE_KEY_AI_RESPONSE_IDLE_TIMEOUT = 'netcatty_ai_response_idle_timeout_v1';
|
||||
export const STORAGE_KEY_AI_MAX_ITERATIONS = 'netcatty_ai_max_iterations_v1';
|
||||
export const STORAGE_KEY_AI_SESSIONS = 'netcatty_ai_sessions_v1';
|
||||
export const STORAGE_KEY_AI_ACTIVE_SESSION_MAP = 'netcatty_ai_active_session_map_v1';
|
||||
export const STORAGE_KEY_AI_AGENT_MODEL_MAP = 'netcatty_ai_agent_model_map_v1';
|
||||
export const STORAGE_KEY_AI_AGENT_PROVIDER_MAP = 'netcatty_ai_agent_provider_map_v1';
|
||||
export const STORAGE_KEY_AI_AGENT_THINKING_MAP = 'netcatty_ai_agent_thinking_map_v1';
|
||||
export const STORAGE_KEY_AI_COMPOSER_MODEL_PREFS = 'netcatty_ai_composer_model_prefs_v1';
|
||||
export const STORAGE_KEY_AI_WEB_SEARCH = 'netcatty_ai_web_search_v1';
|
||||
export const STORAGE_KEY_AI_QUICK_MESSAGES = 'netcatty_ai_quick_messages_v1';
|
||||
/** Confirm-mode permission grant memory (capability + session/command patterns). */
|
||||
export const STORAGE_KEY_AI_PERMISSION_GRANTS = 'netcatty_ai_permission_grants_v1';
|
||||
export const STORAGE_KEY_AI_SHOW_TERMINAL_SELECTION_ACTION = 'netcatty_ai_show_terminal_selection_action_v1';
|
||||
/** External MCP: whether the user last enabled the public catalog MCP endpoint. */
|
||||
export const STORAGE_KEY_AI_EXTERNAL_MCP_ENABLED = 'netcatty_ai_external_mcp_enabled_v1';
|
||||
/** External MCP lifecycle mode: temporary (idle timeout) or persistent (restore on launch). */
|
||||
export const STORAGE_KEY_AI_EXTERNAL_MCP_MODE = 'netcatty_ai_external_mcp_mode_v1';
|
||||
/** External MCP idle timeout in minutes (temporary mode only). */
|
||||
export const STORAGE_KEY_AI_EXTERNAL_MCP_IDLE_TIMEOUT_MINUTES = 'netcatty_ai_external_mcp_idle_timeout_minutes_v1';
|
||||
/** External MCP: whether host_open should surface/focus the main window (default true). */
|
||||
export const STORAGE_KEY_AI_EXTERNAL_MCP_FOCUS_ON_HOST_OPEN = 'netcatty_ai_external_mcp_focus_on_host_open_v1';
|
||||
/** Idle timeout for terminal sessions opened by an AI through host_open. */
|
||||
export const STORAGE_KEY_AI_SESSION_IDLE_TIMEOUT_MINUTES = 'netcatty_ai_session_idle_timeout_minutes_v1';
|
||||
/** External MCP: whether host_open sessions stay hidden from the tab bar (default false). */
|
||||
export const STORAGE_KEY_AI_EXTERNAL_MCP_SILENT_SESSIONS = 'netcatty_ai_external_mcp_silent_sessions_v1';
|
||||
/** AI panel diagnostic hide list (comma-separated part names). */
|
||||
export const STORAGE_KEY_AI_PANEL_DIAGNOSTIC_HIDE = 'netcatty.aiDebug.hide';
|
||||
/** AI panel React profiler toggle. */
|
||||
export const STORAGE_KEY_AI_PANEL_DIAGNOSTIC_PROFILE = 'netcatty.aiDebug.profile';
|
||||
|
||||
// SFTP Transfer Concurrency
|
||||
export const STORAGE_KEY_SFTP_TRANSFER_CONCURRENCY = 'netcatty_sftp_transfer_concurrency_v1';
|
||||
/**
|
||||
* Legacy key only. Folder full-tree pre-scan was removed; values are ignored
|
||||
* so old localStorage / sync payloads do not resurrect a live setting.
|
||||
*/
|
||||
export const STORAGE_KEY_SFTP_FOLDER_PRESCAN = 'netcatty_sftp_folder_prescan_v1';
|
||||
/** Skip files when target size + mtime already match the source (rsync-like). */
|
||||
export const STORAGE_KEY_SFTP_SKIP_UNCHANGED = 'netcatty_sftp_skip_unchanged_v1';
|
||||
/**
|
||||
* @deprecated Legacy transfer-pool idle TTL. No longer read; SSH keep-alive uses
|
||||
* STORAGE_KEY_SSH_TRANSPORT_IDLE_TTL_MS. Kept so old localStorage entries are ignored safely.
|
||||
*/
|
||||
export const STORAGE_KEY_SFTP_TRANSFER_POOL_IDLE_TTL_MS = 'netcatty_sftp_transfer_pool_idle_ttl_ms_v1';
|
||||
/** Shared SSH transport idle park TTL in ms (0 = keep until app quit). */
|
||||
export const STORAGE_KEY_SSH_TRANSPORT_IDLE_TTL_MS = 'netcatty_ssh_transport_idle_ttl_ms_v1';
|
||||
|
||||
// Workspace Focus Indicator Style
|
||||
export const STORAGE_KEY_WORKSPACE_FOCUS_STYLE = 'netcatty_workspace_focus_style_v1';
|
||||
|
||||
// Vault: Show Recently Connected hosts section
|
||||
export const STORAGE_KEY_SHOW_RECENT_HOSTS = 'netcatty_show_recent_hosts_v1';
|
||||
export const STORAGE_KEY_HOST_CLICK_BEHAVIOR = 'netcatty_host_click_behavior_v1';
|
||||
export const STORAGE_KEY_SHOW_ONLY_UNGROUPED_HOSTS_IN_ROOT = 'netcatty_show_only_ungrouped_hosts_in_root_v1';
|
||||
|
||||
// Top tabs: Show standalone SFTP view tab
|
||||
export const STORAGE_KEY_SHOW_SFTP_TAB = 'netcatty_show_sftp_tab_v1';
|
||||
export const STORAGE_KEY_SHOW_HOST_TREE_SIDEBAR = 'netcatty_show_host_tree_sidebar_v1';
|
||||
|
||||
// Shortcuts: Cmd/Ctrl+[1...9] and Ctrl+Tab skip pinned Vault/SFTP tabs
|
||||
export const STORAGE_KEY_SHELL_ONLY_TAB_NUMBER_SHORTCUTS = 'netcatty_shell_only_tab_number_shortcuts_v1';
|
||||
|
||||
// Shortcuts: show 1...9 badge on tabs that match number switch shortcuts
|
||||
export const STORAGE_KEY_SHOW_TAB_NUMBER_BADGES = 'netcatty_show_tab_number_badges_v1';
|
||||
|
||||
// Shortcuts: disable terminal font zoom shortcuts
|
||||
export const STORAGE_KEY_DISABLE_TERMINAL_FONT_ZOOM = 'netcatty_disable_terminal_font_zoom_v1';
|
||||
|
||||
/** Host IDs for which the "enable Network Device Mode" suggestion has already been shown/handled (suggest once per host). */
|
||||
export const STORAGE_KEY_NETWORK_DEVICE_SUGGEST_HANDLED = 'netcatty_network_device_suggest_handled_v1';
|
||||
|
||||
// Group Configurations (default settings inherited by hosts)
|
||||
export const STORAGE_KEY_GROUP_CONFIGS = 'netcatty_group_configs_v1';
|
||||
/** Crash-recovery journal for the plugin importer multi-key Vault commit. */
|
||||
export const STORAGE_KEY_PLUGIN_IMPORT_TRANSACTION = 'netcatty_plugin_import_transaction_v1';
|
||||
|
||||
// Side Panel
|
||||
export const STORAGE_KEY_SIDE_PANEL_WIDTH = 'netcatty_side_panel_width';
|
||||
export const STORAGE_KEY_TERMINAL_SIDE_PANEL_TAB_ORDER = 'netcatty_terminal_side_panel_tab_order_v1';
|
||||
export const STORAGE_KEY_TERMINAL_SIDE_PANEL_AUTO_OPEN = 'netcatty_terminal_side_panel_auto_open_v1';
|
||||
export const STORAGE_KEY_TERMINAL_SIDE_PANEL_AUTO_OPEN_TAB = 'netcatty_terminal_side_panel_auto_open_tab_v1';
|
||||
export const STORAGE_KEY_WORKSPACE_FOCUS_SIDEBAR_WIDTH = 'netcatty_workspace_focus_sidebar_width';
|
||||
export const STORAGE_KEY_TERMINAL_HOST_TREE_WIDTH = 'netcatty_terminal_host_tree_width_v1';
|
||||
export const STORAGE_KEY_TERMINAL_HOST_TREE_COLLAPSED = 'netcatty_terminal_host_tree_collapsed_v1';
|
||||
export const STORAGE_KEY_TERMINAL_COMPOSE_BAR_OPEN = 'netcatty_terminal_compose_bar_open_v1';
|
||||
export const STORAGE_KEY_TERMINAL_SEARCH_OPEN = 'netcatty_terminal_search_open_v1';
|
||||
export const STORAGE_KEY_TERMINAL_ENCODING_BY_HOST_PREFIX = 'netcatty_terminal_encoding_by_host_v1:';
|
||||
export const STORAGE_KEY_TERMINAL_YMODEM_SEND_DIR = 'netcatty_terminal_ymodem_send_dir_v1';
|
||||
|
||||
// Port Forwarding (transient cross-window broadcast key)
|
||||
export const STORAGE_KEY_PF_RECONNECT_CANCEL = '__netcatty_pf_cancel_reconnect';
|
||||
|
||||
// Default SSH Key Passphrases (for ~/.ssh keys not managed in the vault)
|
||||
export const STORAGE_KEY_DEFAULT_KEY_PASSPHRASES = 'netcatty_default_key_passphrases_v1';
|
||||
|
||||
// Plugin sync sidecars / availability. Literals MUST match domain/sync
|
||||
// SYNC_STORAGE_KEYS (PLUGIN_SIDECARS_* / AVAILABLE_PLUGIN_SYNC_PROVIDERS).
|
||||
export const STORAGE_KEY_PLUGIN_SIDECARS_LAST_KNOWN = 'netcatty_plugin_sidecars_last_known_v1';
|
||||
export const STORAGE_KEY_PLUGIN_SIDECARS_PENDING_REMOTE = 'netcatty_plugin_sidecars_pending_remote_v1';
|
||||
export const STORAGE_KEY_AVAILABLE_PLUGIN_SYNC_PROVIDERS = 'netcatty_available_plugin_sync_providers_v1';
|
||||
|
||||
// Debug Flags (no _v1 suffix — developer-only, not persisted data)
|
||||
export const STORAGE_KEY_DEBUG_HOTKEYS = 'debug.hotkeys';
|
||||
export const STORAGE_KEY_DEBUG_UPDATE_DEMO = 'debug.updateDemo';
|
||||
7
infrastructure/config/terminalFlowConstants.cjs
Normal file
7
infrastructure/config/terminalFlowConstants.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Terminal flow-control thresholds (single source: terminalFlowConstants.json).
|
||||
* Aligned with VS Code FlowControlConstants (terminal.ts).
|
||||
*/
|
||||
module.exports = require("./terminalFlowConstants.json");
|
||||
14
infrastructure/config/terminalFlowConstants.json
Normal file
14
infrastructure/config/terminalFlowConstants.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"FLOW_HIGH_WATER_MARK": 1048576,
|
||||
"FLOW_LOW_WATER_MARK": 8192,
|
||||
"FLOW_CHAR_COUNT_ACK_SIZE": 4096,
|
||||
"MAX_PENDING_WRITE_COALESCE_BYTES": 1048576,
|
||||
"MAX_PENDING_WRITE_COALESCE_BYTES_FLOOD": 131072,
|
||||
"MAX_TERMINAL_PLAIN_WRITE_CHUNK_BYTES": 131072,
|
||||
"MAX_TERMINAL_UNBROKEN_WRITE_CHUNK_BYTES": 131072,
|
||||
"MAX_TERMINAL_WRITE_QUEUE_DRAIN_BYTES": 524288,
|
||||
"TERMINAL_LONG_LINE_PRESSURE_BYTES": 65536,
|
||||
"TERMINAL_AUX_LONG_LINE_SCAN_LIMIT_CHARS": 65536,
|
||||
"XTERM_WRITE_CALLBACK_FAST_PATH_MAX_BYTES": 1024,
|
||||
"XTERM_WRITE_CALLBACK_BATCH_BYTES": 100000
|
||||
}
|
||||
48
infrastructure/config/terminalThemes.ts
Normal file
48
infrastructure/config/terminalThemes.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { TerminalTheme } from '../../domain/models';
|
||||
import { classicTerminalThemes } from './terminalThemes/classic';
|
||||
import { systemPresetTerminalThemes } from './terminalThemes/systemPresets';
|
||||
import { coreTerminalThemes } from './terminalThemes/core';
|
||||
import { extraTerminalThemes } from './terminalThemes/extra';
|
||||
import { modernTerminalThemes } from './terminalThemes/modern';
|
||||
import { uiMatchTerminalThemes } from './terminalThemes/uiMatch';
|
||||
|
||||
// Re-export for convenience
|
||||
export type TerminalThemeConfig = TerminalTheme;
|
||||
|
||||
const UI_MATCH_TERMINAL_THEME_IDS = new Set([
|
||||
'ui-snow',
|
||||
'ui-pure-white',
|
||||
'ui-ivory',
|
||||
'ui-mist',
|
||||
'ui-mint',
|
||||
'ui-sand',
|
||||
'ui-lavender',
|
||||
'ui-pure-black',
|
||||
'ui-midnight',
|
||||
'ui-deep-blue',
|
||||
'ui-vscode',
|
||||
'ui-graphite',
|
||||
'ui-obsidian',
|
||||
'ui-forest',
|
||||
]);
|
||||
|
||||
export const TERMINAL_THEMES: TerminalTheme[] = [
|
||||
...coreTerminalThemes,
|
||||
...uiMatchTerminalThemes,
|
||||
...modernTerminalThemes,
|
||||
...classicTerminalThemes,
|
||||
...systemPresetTerminalThemes,
|
||||
...extraTerminalThemes,
|
||||
];
|
||||
|
||||
const TERMINAL_THEME_BY_ID = new Map(TERMINAL_THEMES.map((theme) => [theme.id, theme]));
|
||||
|
||||
export const getBuiltinTerminalThemeById = (themeId: string): TerminalTheme | undefined =>
|
||||
TERMINAL_THEME_BY_ID.get(themeId);
|
||||
|
||||
export const isUiMatchTerminalThemeId = (themeId: string): boolean =>
|
||||
UI_MATCH_TERMINAL_THEME_IDS.has(themeId);
|
||||
|
||||
export const USER_VISIBLE_TERMINAL_THEMES: TerminalTheme[] = TERMINAL_THEMES.filter(
|
||||
(theme) => !isUiMatchTerminalThemeId(theme.id),
|
||||
);
|
||||
652
infrastructure/config/terminalThemes/classic.ts
Normal file
652
infrastructure/config/terminalThemes/classic.ts
Normal file
@@ -0,0 +1,652 @@
|
||||
import type { TerminalTheme } from '../../../domain/models';
|
||||
|
||||
export const classicTerminalThemes: TerminalTheme[] = [
|
||||
{
|
||||
id: 'romania-night',
|
||||
name: 'Romania Night',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#1e1b32',
|
||||
foreground: '#e0b5ff',
|
||||
cursor: '#a78bfa',
|
||||
selection: '#3d2e5e',
|
||||
black: '#0d0a1a',
|
||||
red: '#d96880',
|
||||
green: '#7ac67c',
|
||||
yellow: '#d4b896',
|
||||
blue: '#7ca3d4',
|
||||
magenta: '#a58cd6',
|
||||
cyan: '#90bfe4',
|
||||
white: '#e0b5ff',
|
||||
brightBlack: '#6b5b7f',
|
||||
brightRed: '#d96880',
|
||||
brightGreen: '#7ac67c',
|
||||
brightYellow: '#d4b896',
|
||||
brightBlue: '#7ca3d4',
|
||||
brightMagenta: '#a58cd6',
|
||||
brightCyan: '#90bfe4',
|
||||
brightWhite: '#e0b5ff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'romania-day',
|
||||
name: 'Romania Day',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fdf3f7',
|
||||
foreground: '#5e4c6f',
|
||||
cursor: '#a78bfa',
|
||||
selection: '#e8d4f0',
|
||||
black: '#1e1b32',
|
||||
red: '#a73a5e',
|
||||
green: '#4a7c4e',
|
||||
yellow: '#a38060',
|
||||
blue: '#4a6fa5',
|
||||
magenta: '#6b5b7f',
|
||||
cyan: '#4a7fb2',
|
||||
white: '#f5e0f0',
|
||||
brightBlack: '#8b7c9f',
|
||||
brightRed: '#c75880',
|
||||
brightGreen: '#5a9c5e',
|
||||
brightYellow: '#c49878',
|
||||
brightBlue: '#5a8fb5',
|
||||
brightMagenta: '#8b7b9f',
|
||||
brightCyan: '#5a8fb5',
|
||||
brightWhite: '#5e4c6f'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'aubergine',
|
||||
name: 'Aubergine',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#2d1b3d',
|
||||
foreground: '#d4a5d4',
|
||||
cursor: '#ff8ccc',
|
||||
selection: '#5d3b6d',
|
||||
black: '#1a0f2e',
|
||||
red: '#d46a6a',
|
||||
green: '#8fb06d',
|
||||
yellow: '#d4a574',
|
||||
blue: '#6d99d4',
|
||||
magenta: '#9d7eca',
|
||||
cyan: '#7ecad4',
|
||||
white: '#d4a5d4',
|
||||
brightBlack: '#5d3b6d',
|
||||
brightRed: '#ff8888',
|
||||
brightGreen: '#9fc66d',
|
||||
brightYellow: '#ffc585',
|
||||
brightBlue: '#8db5ff',
|
||||
brightMagenta: '#c9a5ff',
|
||||
brightCyan: '#a5e6ff',
|
||||
brightWhite: '#ffd4ff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'peach-fresh',
|
||||
name: 'Peach Fresh',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fef5ed',
|
||||
foreground: '#5d4037',
|
||||
cursor: '#ff8a65',
|
||||
selection: '#ffe0b2',
|
||||
black: '#2c2416',
|
||||
red: '#d84315',
|
||||
green: '#558b2f',
|
||||
yellow: '#f57f17',
|
||||
blue: '#01579b',
|
||||
magenta: '#6a1b9a',
|
||||
cyan: '#00838f',
|
||||
white: '#f5deb3',
|
||||
brightBlack: '#8d6e63',
|
||||
brightRed: '#ff7043',
|
||||
brightGreen: '#7cb342',
|
||||
brightYellow: '#fbc02d',
|
||||
brightBlue: '#0277bd',
|
||||
brightMagenta: '#8e24aa',
|
||||
brightCyan: '#00acc1',
|
||||
brightWhite: '#ffccbc'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '1984-dark',
|
||||
name: '1984 Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0c1014',
|
||||
foreground: '#d4d4d4',
|
||||
cursor: '#8fb5d9',
|
||||
selection: '#1b2d42',
|
||||
black: '#000000',
|
||||
red: '#d71920',
|
||||
green: '#00a600',
|
||||
yellow: '#a6a614',
|
||||
blue: '#2e7de8',
|
||||
magenta: '#8e35ef',
|
||||
cyan: '#5ebdab',
|
||||
white: '#d4d4d4',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#ff6565',
|
||||
brightGreen: '#66ff66',
|
||||
brightYellow: '#ffff66',
|
||||
brightBlue: '#6699ff',
|
||||
brightMagenta: '#ff99ff',
|
||||
brightCyan: '#99ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '1984-light',
|
||||
name: '1984 Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#3d3d3d',
|
||||
cursor: '#333333',
|
||||
selection: '#e8e8e8',
|
||||
black: '#000000',
|
||||
red: '#d71920',
|
||||
green: '#00a600',
|
||||
yellow: '#a6a614',
|
||||
blue: '#2e7de8',
|
||||
magenta: '#8e35ef',
|
||||
cyan: '#5ebdab',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#999999',
|
||||
brightRed: '#ff6565',
|
||||
brightGreen: '#66ff66',
|
||||
brightYellow: '#ffff66',
|
||||
brightBlue: '#6699ff',
|
||||
brightMagenta: '#ff99ff',
|
||||
brightCyan: '#99ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'winter-night',
|
||||
name: 'Winter Night',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#c9d1d9',
|
||||
cursor: '#79c0ff',
|
||||
selection: '#1f6feb',
|
||||
black: '#0d1117',
|
||||
red: '#f85149',
|
||||
green: '#3fb950',
|
||||
yellow: '#d29922',
|
||||
blue: '#79c0ff',
|
||||
magenta: '#bc8ef4',
|
||||
cyan: '#39c5cf',
|
||||
white: '#c9d1d9',
|
||||
brightBlack: '#30363d',
|
||||
brightRed: '#f85149',
|
||||
brightGreen: '#3fb950',
|
||||
brightYellow: '#d29922',
|
||||
brightBlue: '#79c0ff',
|
||||
brightMagenta: '#bc8ef4',
|
||||
brightCyan: '#39c5cf',
|
||||
brightWhite: '#c9d1d9'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'winter-day',
|
||||
name: 'Winter Day',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#24292f',
|
||||
cursor: '#0969da',
|
||||
selection: '#e1e9f0',
|
||||
black: '#24292f',
|
||||
red: '#cf222e',
|
||||
green: '#1a7f37',
|
||||
yellow: '#9e6a03',
|
||||
blue: '#0969da',
|
||||
magenta: '#8250df',
|
||||
cyan: '#0884d9',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#57606a',
|
||||
brightRed: '#d1242f',
|
||||
brightGreen: '#238636',
|
||||
brightYellow: '#b08900',
|
||||
brightBlue: '#388bfd',
|
||||
brightMagenta: '#a371f7',
|
||||
brightCyan: '#56c2e6',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'tokyo-night',
|
||||
name: 'Tokyo Night',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#1a1b26',
|
||||
foreground: '#c0caf5',
|
||||
cursor: '#7dcfff',
|
||||
selection: '#3c5870',
|
||||
black: '#15161e',
|
||||
red: '#f7768e',
|
||||
green: '#9ece6a',
|
||||
yellow: '#e0af68',
|
||||
blue: '#7aa2f7',
|
||||
magenta: '#bb9af7',
|
||||
cyan: '#7dcfff',
|
||||
white: '#a9b1d6',
|
||||
brightBlack: '#414868',
|
||||
brightRed: '#f7768e',
|
||||
brightGreen: '#9ece6a',
|
||||
brightYellow: '#e0af68',
|
||||
brightBlue: '#7aa2f7',
|
||||
brightMagenta: '#bb9af7',
|
||||
brightCyan: '#7dcfff',
|
||||
brightWhite: '#c0caf5'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'tokyo-day',
|
||||
name: 'Tokyo Day',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#3760bf',
|
||||
cursor: '#188092',
|
||||
selection: '#e5e1ed',
|
||||
black: '#1f2335',
|
||||
red: '#f52a65',
|
||||
green: '#587539',
|
||||
yellow: '#8e8a2b',
|
||||
blue: '#188092',
|
||||
magenta: '#8c6cb8',
|
||||
cyan: '#007197',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#9699a3',
|
||||
brightRed: '#f52a65',
|
||||
brightGreen: '#587539',
|
||||
brightYellow: '#8e8a2b',
|
||||
brightBlue: '#188092',
|
||||
brightMagenta: '#8c6cb8',
|
||||
brightCyan: '#007197',
|
||||
brightWhite: '#3760bf'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'catppuccin-latte',
|
||||
name: 'Catppuccin Latte',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#eff1f5',
|
||||
foreground: '#4c4f69',
|
||||
cursor: '#dc8744',
|
||||
selection: '#e6e9ef',
|
||||
black: '#5c5f77',
|
||||
red: '#d20f39',
|
||||
green: '#40a02b',
|
||||
yellow: '#df8e1d',
|
||||
blue: '#1e66f5',
|
||||
magenta: '#ea76cb',
|
||||
cyan: '#04a5e5',
|
||||
white: '#eff1f5',
|
||||
brightBlack: '#6c6f85',
|
||||
brightRed: '#d20f39',
|
||||
brightGreen: '#40a02b',
|
||||
brightYellow: '#df8e1d',
|
||||
brightBlue: '#1e66f5',
|
||||
brightMagenta: '#ea76cb',
|
||||
brightCyan: '#04a5e5',
|
||||
brightWhite: '#bcc0cc'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'catppuccin-mocha',
|
||||
name: 'Catppuccin Mocha',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#1e1e2e',
|
||||
foreground: '#cdd6f4',
|
||||
cursor: '#f5e0dc',
|
||||
selection: '#313244',
|
||||
black: '#45475a',
|
||||
red: '#f38ba8',
|
||||
green: '#a6e3a1',
|
||||
yellow: '#f9e2af',
|
||||
blue: '#89b4fa',
|
||||
magenta: '#f5c2de',
|
||||
cyan: '#94e2d5',
|
||||
white: '#cdd6f4',
|
||||
brightBlack: '#585b70',
|
||||
brightRed: '#f38ba8',
|
||||
brightGreen: '#a6e3a1',
|
||||
brightYellow: '#f9e2af',
|
||||
brightBlue: '#89b4fa',
|
||||
brightMagenta: '#f5c2de',
|
||||
brightCyan: '#94e2d5',
|
||||
brightWhite: '#cdd6f4'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'diwali',
|
||||
name: 'Diwali',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#10100f',
|
||||
foreground: '#e0e0e0',
|
||||
cursor: '#ffb627',
|
||||
selection: '#33332d',
|
||||
black: '#1a1a1a',
|
||||
red: '#d30102',
|
||||
green: '#2e8c00',
|
||||
yellow: '#d4af37',
|
||||
blue: '#01a0e4',
|
||||
magenta: '#ff00ff',
|
||||
cyan: '#00ffff',
|
||||
white: '#e0e0e0',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#ff3333',
|
||||
brightGreen: '#33ff33',
|
||||
brightYellow: '#ffff33',
|
||||
brightBlue: '#3399ff',
|
||||
brightMagenta: '#ff33ff',
|
||||
brightCyan: '#33ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'movember',
|
||||
name: 'Movember',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#2d1b00',
|
||||
foreground: '#d4a574',
|
||||
cursor: '#d4a574',
|
||||
selection: '#5d3b1a',
|
||||
black: '#1a0f00',
|
||||
red: '#c85a54',
|
||||
green: '#9fb154',
|
||||
yellow: '#d4a574',
|
||||
blue: '#8fa3b9',
|
||||
magenta: '#a18a8e',
|
||||
cyan: '#9a8f7f',
|
||||
white: '#d4a574',
|
||||
brightBlack: '#8d6e63',
|
||||
brightRed: '#d87566',
|
||||
brightGreen: '#b5d376',
|
||||
brightYellow: '#ffc77d',
|
||||
brightBlue: '#9fb154',
|
||||
brightMagenta: '#c5949e',
|
||||
brightCyan: '#a5a0a0',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'atom-one-dark',
|
||||
name: 'Atom One Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#282c34',
|
||||
foreground: '#abb2bf',
|
||||
cursor: '#528b8b',
|
||||
selection: '#3e4451',
|
||||
black: '#1e1e1e',
|
||||
red: '#e06c75',
|
||||
green: '#98c379',
|
||||
yellow: '#d19a66',
|
||||
blue: '#61afef',
|
||||
magenta: '#c678dd',
|
||||
cyan: '#56b6c2',
|
||||
white: '#abb2bf',
|
||||
brightBlack: '#5c6370',
|
||||
brightRed: '#e06c75',
|
||||
brightGreen: '#98c379',
|
||||
brightYellow: '#d19a66',
|
||||
brightBlue: '#61afef',
|
||||
brightMagenta: '#c678dd',
|
||||
brightCyan: '#56b6c2',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'atom-one-light',
|
||||
name: 'Atom One Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fafafa',
|
||||
foreground: '#383a42',
|
||||
cursor: '#4078f2',
|
||||
selection: '#e5ebf1',
|
||||
black: '#383a42',
|
||||
red: '#e45649',
|
||||
green: '#50a14f',
|
||||
yellow: '#c18401',
|
||||
blue: '#4078f2',
|
||||
magenta: '#a626a4',
|
||||
cyan: '#0184bc',
|
||||
white: '#fafafa',
|
||||
brightBlack: '#a0a1a7',
|
||||
brightRed: '#e45649',
|
||||
brightGreen: '#50a14f',
|
||||
brightYellow: '#c18401',
|
||||
brightBlue: '#4078f2',
|
||||
brightMagenta: '#a626a4',
|
||||
brightCyan: '#0184bc',
|
||||
brightWhite: '#383a42'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'halloween',
|
||||
name: 'Halloween',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#3d1f33',
|
||||
foreground: '#f6d5a8',
|
||||
cursor: '#ff6600',
|
||||
selection: '#6d3f5f',
|
||||
black: '#1a0f33',
|
||||
red: '#ff3300',
|
||||
green: '#ffcc00',
|
||||
yellow: '#ff9900',
|
||||
blue: '#6600cc',
|
||||
magenta: '#ff0066',
|
||||
cyan: '#00ffcc',
|
||||
white: '#f6d5a8',
|
||||
brightBlack: '#8b5a7d',
|
||||
brightRed: '#ff6633',
|
||||
brightGreen: '#ffff33',
|
||||
brightYellow: '#ffcc33',
|
||||
brightBlue: '#9933ff',
|
||||
brightMagenta: '#ff33cc',
|
||||
brightCyan: '#33ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'dia-de-muertos',
|
||||
name: 'Dia De Muertos',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fef4e9',
|
||||
foreground: '#4a4040',
|
||||
cursor: '#ff6b35',
|
||||
selection: '#ffe5cc',
|
||||
black: '#2d1810',
|
||||
red: '#f05a28',
|
||||
green: '#04a777',
|
||||
yellow: '#f7931e',
|
||||
blue: '#003d7a',
|
||||
magenta: '#6b4c8a',
|
||||
cyan: '#008b8b',
|
||||
white: '#f5e6d3',
|
||||
brightBlack: '#8b7355',
|
||||
brightRed: '#ff7043',
|
||||
brightGreen: '#4caf50',
|
||||
brightYellow: '#fbc02d',
|
||||
brightBlue: '#2196f3',
|
||||
brightMagenta: '#9c27b0',
|
||||
brightCyan: '#00bcd4',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gruvbox-dark',
|
||||
name: 'Gruvbox Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#282828',
|
||||
foreground: '#ebdbb2',
|
||||
cursor: '#ebdbb2',
|
||||
selection: '#504945',
|
||||
black: '#282828',
|
||||
red: '#cc241d',
|
||||
green: '#98971a',
|
||||
yellow: '#d79921',
|
||||
blue: '#458588',
|
||||
magenta: '#b16286',
|
||||
cyan: '#689d6a',
|
||||
white: '#a89984',
|
||||
brightBlack: '#928374',
|
||||
brightRed: '#fb4934',
|
||||
brightGreen: '#b8bb26',
|
||||
brightYellow: '#fabd2f',
|
||||
brightBlue: '#83a598',
|
||||
brightMagenta: '#d3869b',
|
||||
brightCyan: '#8ec07c',
|
||||
brightWhite: '#ebdbb2'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'gruvbox-light',
|
||||
name: 'Gruvbox Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fbf1c7',
|
||||
foreground: '#3c3836',
|
||||
cursor: '#3c3836',
|
||||
selection: '#f2e5bc',
|
||||
black: '#fbf1c7',
|
||||
red: '#cc241d',
|
||||
green: '#98971a',
|
||||
yellow: '#d79921',
|
||||
blue: '#458588',
|
||||
magenta: '#b16286',
|
||||
cyan: '#689d6a',
|
||||
white: '#7c6f64',
|
||||
brightBlack: '#928374',
|
||||
brightRed: '#fb4934',
|
||||
brightGreen: '#b8bb26',
|
||||
brightYellow: '#fabd2f',
|
||||
brightBlue: '#83a598',
|
||||
brightMagenta: '#d3869b',
|
||||
brightCyan: '#8ec07c',
|
||||
brightWhite: '#3c3836'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'material-dark',
|
||||
name: 'Material Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#263238',
|
||||
foreground: '#eeffff',
|
||||
cursor: '#eeffff',
|
||||
selection: '#37474f',
|
||||
black: '#000000',
|
||||
red: '#f07178',
|
||||
green: '#c3e88d',
|
||||
yellow: '#ffcb8b',
|
||||
blue: '#82b1ff',
|
||||
magenta: '#c792ea',
|
||||
cyan: '#89ddff',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#546e7a',
|
||||
brightRed: '#ef5350',
|
||||
brightGreen: '#9ccc65',
|
||||
brightYellow: '#ffeb3b',
|
||||
brightBlue: '#42a5f5',
|
||||
brightMagenta: '#ab47bc',
|
||||
brightCyan: '#29b6f6',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'material-light',
|
||||
name: 'Material Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#263238',
|
||||
cursor: '#263238',
|
||||
selection: '#eeebee',
|
||||
black: '#000000',
|
||||
red: '#e53935',
|
||||
green: '#43a047',
|
||||
yellow: '#fb8c00',
|
||||
blue: '#1e88e5',
|
||||
magenta: '#8e24aa',
|
||||
cyan: '#00acc1',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#bdbdbd',
|
||||
brightRed: '#ef5350',
|
||||
brightGreen: '#66bb6a',
|
||||
brightYellow: '#ffb74d',
|
||||
brightBlue: '#42a5f5',
|
||||
brightMagenta: '#ab47bc',
|
||||
brightCyan: '#26c6da',
|
||||
brightWhite: '#263238'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'manhatta',
|
||||
name: 'Manhattan',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#1f1f1f',
|
||||
foreground: '#e3e3e3',
|
||||
cursor: '#c4c4c4',
|
||||
selection: '#383838',
|
||||
black: '#000000',
|
||||
red: '#cc0000',
|
||||
green: '#4e9e00',
|
||||
yellow: '#ffff00',
|
||||
blue: '#0066ff',
|
||||
magenta: '#ff00ff',
|
||||
cyan: '#00ffff',
|
||||
white: '#e3e3e3',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#0099ff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'plastic-world',
|
||||
name: 'Plastic World',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#ec008c',
|
||||
foreground: '#00bfff',
|
||||
cursor: '#00bfff',
|
||||
selection: '#dc00c0',
|
||||
black: '#000000',
|
||||
red: '#ff006e',
|
||||
green: '#00ff00',
|
||||
yellow: '#ffff00',
|
||||
blue: '#00bfff',
|
||||
magenta: '#ff00ff',
|
||||
cyan: '#00ffff',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#999999',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#0099ff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
];
|
||||
58
infrastructure/config/terminalThemes/core.ts
Normal file
58
infrastructure/config/terminalThemes/core.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { TerminalTheme } from '../../../domain/models';
|
||||
|
||||
export const coreTerminalThemes: TerminalTheme[] = [
|
||||
{
|
||||
id: 'netcatty-dark',
|
||||
name: 'Netcatty Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#c9d1d9',
|
||||
cursor: '#58a6ff',
|
||||
selection: '#264f78',
|
||||
black: '#0d1117',
|
||||
red: '#ff7b72',
|
||||
green: '#3fb950',
|
||||
yellow: '#d29922',
|
||||
blue: '#58a6ff',
|
||||
magenta: '#bc8cff',
|
||||
cyan: '#39c5cf',
|
||||
white: '#b1bac4',
|
||||
brightBlack: '#6e7681',
|
||||
brightRed: '#ffa198',
|
||||
brightGreen: '#56d364',
|
||||
brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff',
|
||||
brightMagenta: '#d2a8ff',
|
||||
brightCyan: '#56d4dd',
|
||||
brightWhite: '#f0f6fc'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'netcatty-light',
|
||||
name: 'Netcatty Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#f6f8fa',
|
||||
foreground: '#24292f',
|
||||
cursor: '#0969da',
|
||||
selection: '#add6ff',
|
||||
black: '#24292f',
|
||||
red: '#cf222e',
|
||||
green: '#116329',
|
||||
yellow: '#9a6700',
|
||||
blue: '#0969da',
|
||||
magenta: '#8250df',
|
||||
cyan: '#0e7574',
|
||||
white: '#6e7781',
|
||||
brightBlack: '#57606a',
|
||||
brightRed: '#a40e26',
|
||||
brightGreen: '#1a7f37',
|
||||
brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff',
|
||||
brightMagenta: '#a475f9',
|
||||
brightCyan: '#0c7875',
|
||||
brightWhite: '#8c959f'
|
||||
}
|
||||
},
|
||||
];
|
||||
841
infrastructure/config/terminalThemes/extra.ts
Normal file
841
infrastructure/config/terminalThemes/extra.ts
Normal file
@@ -0,0 +1,841 @@
|
||||
import type { TerminalTheme } from '../../../domain/models';
|
||||
|
||||
export const extraTerminalThemes: TerminalTheme[] = [
|
||||
{
|
||||
id: 'basic',
|
||||
name: 'Basic',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#000000',
|
||||
cursor: '#000000',
|
||||
selection: '#e8e8e8',
|
||||
black: '#000000',
|
||||
red: '#990000',
|
||||
green: '#009900',
|
||||
yellow: '#999900',
|
||||
blue: '#000099',
|
||||
magenta: '#990099',
|
||||
cyan: '#009999',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#0000ff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'homebrew',
|
||||
name: 'Homebrew',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#000000',
|
||||
foreground: '#00ff00',
|
||||
cursor: '#00a6b2',
|
||||
selection: '#00383d',
|
||||
black: '#000000',
|
||||
red: '#990000',
|
||||
green: '#00a600',
|
||||
yellow: '#999900',
|
||||
blue: '#4d72ff',
|
||||
magenta: '#b200b2',
|
||||
cyan: '#00a6b2',
|
||||
white: '#bfbfbf',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#e50000',
|
||||
brightGreen: '#00d900',
|
||||
brightYellow: '#e5e500',
|
||||
brightBlue: '#5a7aff',
|
||||
brightMagenta: '#e500e5',
|
||||
brightCyan: '#00e5e5',
|
||||
brightWhite: '#e5e5e5'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'grass',
|
||||
name: 'Grass',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#a4e400',
|
||||
foreground: '#333333',
|
||||
cursor: '#333333',
|
||||
selection: '#ccff00',
|
||||
black: '#000000',
|
||||
red: '#cc0000',
|
||||
green: '#00cc00',
|
||||
yellow: '#ffff00',
|
||||
blue: '#0000ff',
|
||||
magenta: '#ff00ff',
|
||||
cyan: '#00ffff',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#999999',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#0000ff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'man-page',
|
||||
name: 'Man Page',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fef5e7',
|
||||
foreground: '#2d2d2d',
|
||||
cursor: '#2d2d2d',
|
||||
selection: '#e8e8d0',
|
||||
black: '#000000',
|
||||
red: '#cc0000',
|
||||
green: '#009900',
|
||||
yellow: '#999900',
|
||||
blue: '#000099',
|
||||
magenta: '#990099',
|
||||
cyan: '#009999',
|
||||
white: '#cccccc',
|
||||
brightBlack: '#999999',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#0000ff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'novel',
|
||||
name: 'Novel',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#eae6d8',
|
||||
foreground: '#3d2817',
|
||||
cursor: '#3d2817',
|
||||
selection: '#ddd8ca',
|
||||
black: '#2c2c2c',
|
||||
red: '#8b0000',
|
||||
green: '#006400',
|
||||
yellow: '#8b8b00',
|
||||
blue: '#00008b',
|
||||
magenta: '#8b008b',
|
||||
cyan: '#008b8b',
|
||||
white: '#d3d3d3',
|
||||
brightBlack: '#696969',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#0000ff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ocean',
|
||||
name: 'Ocean',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#000033',
|
||||
foreground: '#ffffff',
|
||||
cursor: '#0066ff',
|
||||
selection: '#003366',
|
||||
black: '#000000',
|
||||
red: '#ff0000',
|
||||
green: '#00ff00',
|
||||
yellow: '#ffff00',
|
||||
blue: '#0066ff',
|
||||
magenta: '#ff00ff',
|
||||
cyan: '#00ffff',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#ff6666',
|
||||
brightGreen: '#66ff66',
|
||||
brightYellow: '#ffff66',
|
||||
brightBlue: '#6699ff',
|
||||
brightMagenta: '#ff66ff',
|
||||
brightCyan: '#66ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'pro',
|
||||
name: 'Pro',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#000000',
|
||||
foreground: '#ffffff',
|
||||
cursor: '#ffffff',
|
||||
selection: '#333333',
|
||||
black: '#000000',
|
||||
red: '#cc0000',
|
||||
green: '#00cc00',
|
||||
yellow: '#cccc00',
|
||||
blue: '#0000ff',
|
||||
magenta: '#ff00ff',
|
||||
cyan: '#00ffff',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#666666',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff00',
|
||||
brightYellow: '#ffff00',
|
||||
brightBlue: '#0000ff',
|
||||
brightMagenta: '#ff00ff',
|
||||
brightCyan: '#00ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'red-sands',
|
||||
name: 'Red Sands',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#3a2e2e',
|
||||
foreground: '#d4a9a0',
|
||||
cursor: '#f7f0e0',
|
||||
selection: '#5d3f3f',
|
||||
black: '#1a0f0f',
|
||||
red: '#cc4d4d',
|
||||
green: '#9fa877',
|
||||
yellow: '#d4b896',
|
||||
blue: '#7a9a9f',
|
||||
magenta: '#a08080',
|
||||
cyan: '#8b9f9f',
|
||||
white: '#d4a9a0',
|
||||
brightBlack: '#8b6f6f',
|
||||
brightRed: '#ff6666',
|
||||
brightGreen: '#b5d376',
|
||||
brightYellow: '#ffc77d',
|
||||
brightBlue: '#9fb5b5',
|
||||
brightMagenta: '#c5a0a0',
|
||||
brightCyan: '#a5b5b5',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'solarized-dark',
|
||||
name: 'Solarized Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#002b36',
|
||||
foreground: '#839496',
|
||||
cursor: '#839496',
|
||||
selection: '#073642',
|
||||
black: '#073642',
|
||||
red: '#dc322f',
|
||||
green: '#859900',
|
||||
yellow: '#b58900',
|
||||
blue: '#268bd2',
|
||||
magenta: '#d33682',
|
||||
cyan: '#2aa198',
|
||||
white: '#eee8d5',
|
||||
brightBlack: '#002b36',
|
||||
brightRed: '#cb4b16',
|
||||
brightGreen: '#586e75',
|
||||
brightYellow: '#657b83',
|
||||
brightBlue: '#839496',
|
||||
brightMagenta: '#6c71c4',
|
||||
brightCyan: '#93a1a1',
|
||||
brightWhite: '#fdf6e3'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'solarized-light',
|
||||
name: 'Solarized Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fdf6e3',
|
||||
foreground: '#657b83',
|
||||
cursor: '#657b83',
|
||||
selection: '#eee8d5',
|
||||
black: '#073642',
|
||||
red: '#dc322f',
|
||||
green: '#859900',
|
||||
yellow: '#b58900',
|
||||
blue: '#268bd2',
|
||||
magenta: '#d33682',
|
||||
cyan: '#2aa198',
|
||||
white: '#fdf6e3',
|
||||
brightBlack: '#586e75',
|
||||
brightRed: '#cb4b16',
|
||||
brightGreen: '#586e75',
|
||||
brightYellow: '#657b83',
|
||||
brightBlue: '#839496',
|
||||
brightMagenta: '#6c71c4',
|
||||
brightCyan: '#93a1a1',
|
||||
brightWhite: '#fdf6e3'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'silver-aerogel',
|
||||
name: 'Silver Aerogel',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#d4d4d4',
|
||||
foreground: '#4d4d4d',
|
||||
cursor: '#4d4d4d',
|
||||
selection: '#c8c8c8',
|
||||
black: '#2d2d2d',
|
||||
red: '#d4666e',
|
||||
green: '#8fa877',
|
||||
yellow: '#d4b896',
|
||||
blue: '#7a9a9f',
|
||||
magenta: '#a08080',
|
||||
cyan: '#8b9f9f',
|
||||
white: '#d4d4d4',
|
||||
brightBlack: '#8b8b8b',
|
||||
brightRed: '#ff7f7f',
|
||||
brightGreen: '#b5d376',
|
||||
brightYellow: '#ffc77d',
|
||||
brightBlue: '#9fb5b5',
|
||||
brightMagenta: '#c5a0a0',
|
||||
brightCyan: '#a5b5b5',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'dracula',
|
||||
name: 'Dracula',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#282a36',
|
||||
foreground: '#f8f8f2',
|
||||
cursor: '#f8f8f2',
|
||||
selection: '#44475a',
|
||||
black: '#000000',
|
||||
red: '#ff5555',
|
||||
green: '#50fa7b',
|
||||
yellow: '#f1fa8c',
|
||||
blue: '#61bfff',
|
||||
magenta: '#ff79c6',
|
||||
cyan: '#8be9fd',
|
||||
white: '#bfbfbf',
|
||||
brightBlack: '#575b86',
|
||||
brightRed: '#ff6e6e',
|
||||
brightGreen: '#69ff94',
|
||||
brightYellow: '#ffffa5',
|
||||
brightBlue: '#8be9fd',
|
||||
brightMagenta: '#ff92df',
|
||||
brightCyan: '#a4ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'monokai',
|
||||
name: 'Monokai',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#272822',
|
||||
foreground: '#f8f8f2',
|
||||
cursor: '#f8f8f0',
|
||||
selection: '#49483e',
|
||||
black: '#272822',
|
||||
red: '#f92672',
|
||||
green: '#a6e22e',
|
||||
yellow: '#e6db74',
|
||||
blue: '#66d9ef',
|
||||
magenta: '#ae81ff',
|
||||
cyan: '#a1efe4',
|
||||
white: '#f8f8f2',
|
||||
brightBlack: '#75715e',
|
||||
brightRed: '#f92672',
|
||||
brightGreen: '#a6e22e',
|
||||
brightYellow: '#e6db74',
|
||||
brightBlue: '#66d9ef',
|
||||
brightMagenta: '#ae81ff',
|
||||
brightCyan: '#a1efe4',
|
||||
brightWhite: '#f9f8f5'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'nord-light',
|
||||
name: 'Nord Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#eceff4',
|
||||
foreground: '#2e3440',
|
||||
cursor: '#2e3440',
|
||||
selection: '#d8dee9',
|
||||
black: '#2e3440',
|
||||
red: '#bf616a',
|
||||
green: '#a3be8c',
|
||||
yellow: '#ebcb8b',
|
||||
blue: '#81a1c1',
|
||||
magenta: '#b48ead',
|
||||
cyan: '#88c0d0',
|
||||
white: '#eceff4',
|
||||
brightBlack: '#4c566a',
|
||||
brightRed: '#bf616a',
|
||||
brightGreen: '#a3be8c',
|
||||
brightYellow: '#ebcb8b',
|
||||
brightBlue: '#81a1c1',
|
||||
brightMagenta: '#b48ead',
|
||||
brightCyan: '#88c0d0',
|
||||
brightWhite: '#2e3440'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'nord-dark',
|
||||
name: 'Nord Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#2e3440',
|
||||
foreground: '#d8dee9',
|
||||
cursor: '#eceff4',
|
||||
selection: '#434c5e',
|
||||
black: '#3b4252',
|
||||
red: '#bf616a',
|
||||
green: '#a3be8c',
|
||||
yellow: '#ebcb8b',
|
||||
blue: '#81a1c1',
|
||||
magenta: '#b48ead',
|
||||
cyan: '#88c0d0',
|
||||
white: '#d8dee9',
|
||||
brightBlack: '#4c566a',
|
||||
brightRed: '#bf616a',
|
||||
brightGreen: '#a3be8c',
|
||||
brightYellow: '#ebcb8b',
|
||||
brightBlue: '#81a1c1',
|
||||
brightMagenta: '#b48ead',
|
||||
brightCyan: '#88c0d0',
|
||||
brightWhite: '#eceff4'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'flexoki-light',
|
||||
name: 'Flexoki Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#FFFCF0',
|
||||
foreground: '#423F34',
|
||||
cursor: '#423F34',
|
||||
selection: '#F3EDE3',
|
||||
black: '#423F34',
|
||||
red: '#D74E35',
|
||||
green: '#6E8326',
|
||||
yellow: '#D0A215',
|
||||
blue: '#4385BE',
|
||||
magenta: '#8B7EC8',
|
||||
cyan: '#3AA99F',
|
||||
white: '#FFFCF0',
|
||||
brightBlack: '#9B9582',
|
||||
brightRed: '#E0A76F',
|
||||
brightGreen: '#879A39',
|
||||
brightYellow: '#E5C335',
|
||||
brightBlue: '#6BA3D4',
|
||||
brightMagenta: '#AE97D8',
|
||||
brightCyan: '#5CB9B5',
|
||||
brightWhite: '#423F34'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'kanagawa-lotus',
|
||||
name: 'Kanagawa Lotus',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#F4F1DE',
|
||||
foreground: '#545464',
|
||||
cursor: '#C4B28A',
|
||||
selection: '#E2DCC5',
|
||||
black: '#2B2D2E',
|
||||
red: '#C34043',
|
||||
green: '#6B8E71',
|
||||
yellow: '#C0A36E',
|
||||
blue: '#7FB4CA',
|
||||
magenta: '#957FB8',
|
||||
cyan: '#7AA89F',
|
||||
white: '#DCD7BA',
|
||||
brightBlack: '#8A8C8E',
|
||||
brightRed: '#D67878',
|
||||
brightGreen: '#98BB6C',
|
||||
brightYellow: '#E6C384',
|
||||
brightBlue: '#7FB4CA',
|
||||
brightMagenta: '#938AA9',
|
||||
brightCyan: '#7AA89F',
|
||||
brightWhite: '#f4f1de'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'everforest-dark',
|
||||
name: 'Everforest Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#2d353b',
|
||||
foreground: '#d3c6aa',
|
||||
cursor: '#a7c957',
|
||||
selection: '#3d484d',
|
||||
black: '#0d0d0d',
|
||||
red: '#f85552',
|
||||
green: '#a7c957',
|
||||
yellow: '#dbc074',
|
||||
blue: '#7fbbb3',
|
||||
magenta: '#d699b6',
|
||||
cyan: '#83c092',
|
||||
white: '#d3c6aa',
|
||||
brightBlack: '#859289',
|
||||
brightRed: '#f85552',
|
||||
brightGreen: '#a7c957',
|
||||
brightYellow: '#dbc074',
|
||||
brightBlue: '#7fbbb3',
|
||||
brightMagenta: '#d699b6',
|
||||
brightCyan: '#83c092',
|
||||
brightWhite: '#d3c6aa'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'everforest-light',
|
||||
name: 'Everforest Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#faf4ed',
|
||||
foreground: '#5c6d64',
|
||||
cursor: '#7cb342',
|
||||
selection: '#e8ddc9',
|
||||
black: '#3d2817',
|
||||
red: '#f85149',
|
||||
green: '#7cb342',
|
||||
yellow: '#d4a574',
|
||||
blue: '#4a7fa7',
|
||||
magenta: '#b58e8e',
|
||||
cyan: '#597445',
|
||||
white: '#faf4ed',
|
||||
brightBlack: '#9da9a0',
|
||||
brightRed: '#f85552',
|
||||
brightGreen: '#a7c957',
|
||||
brightYellow: '#dbc074',
|
||||
brightBlue: '#7fbbb3',
|
||||
brightMagenta: '#d699b6',
|
||||
brightCyan: '#83c092',
|
||||
brightWhite: '#5c6d64'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'github-dark',
|
||||
name: 'GitHub Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#e6edf3',
|
||||
cursor: '#2f81f7',
|
||||
selection: '#264f78',
|
||||
black: '#484f58',
|
||||
red: '#ff7b72',
|
||||
green: '#3fb950',
|
||||
yellow: '#d29922',
|
||||
blue: '#58a6ff',
|
||||
magenta: '#bc8cff',
|
||||
cyan: '#39c5cf',
|
||||
white: '#b1bac4',
|
||||
brightBlack: '#6e7681',
|
||||
brightRed: '#ffa198',
|
||||
brightGreen: '#56d364',
|
||||
brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff',
|
||||
brightMagenta: '#d2a8ff',
|
||||
brightCyan: '#56d4dd',
|
||||
brightWhite: '#ffffff',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'github-light',
|
||||
name: 'GitHub Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#1f2328',
|
||||
cursor: '#0969da',
|
||||
selection: '#add6ff',
|
||||
black: '#24292f',
|
||||
red: '#cf222e',
|
||||
green: '#116329',
|
||||
yellow: '#4d2d00',
|
||||
blue: '#0969da',
|
||||
magenta: '#8250df',
|
||||
cyan: '#1b7c83',
|
||||
white: '#6e7781',
|
||||
brightBlack: '#57606a',
|
||||
brightRed: '#a40e26',
|
||||
brightGreen: '#1a7f37',
|
||||
brightYellow: '#633c01',
|
||||
brightBlue: '#218bff',
|
||||
brightMagenta: '#a475f9',
|
||||
brightCyan: '#3192aa',
|
||||
brightWhite: '#8c959f',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ubuntu',
|
||||
name: 'Ubuntu',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#300a24',
|
||||
foreground: '#eeeeec',
|
||||
cursor: '#bbbbbb',
|
||||
selection: '#b5d5ff',
|
||||
black: '#2e3436',
|
||||
red: '#cc0000',
|
||||
green: '#4e9a06',
|
||||
yellow: '#c4a000',
|
||||
blue: '#3465a4',
|
||||
magenta: '#75507b',
|
||||
cyan: '#06989a',
|
||||
white: '#d3d7cf',
|
||||
brightBlack: '#555753',
|
||||
brightRed: '#ef2929',
|
||||
brightGreen: '#8ae234',
|
||||
brightYellow: '#fce94f',
|
||||
brightBlue: '#729fcf',
|
||||
brightMagenta: '#ad7fa8',
|
||||
brightCyan: '#34e2e2',
|
||||
brightWhite: '#eeeeec',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'one-dark-pro',
|
||||
name: 'One Dark Pro',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#282c34',
|
||||
foreground: '#abb2bf',
|
||||
cursor: '#528bff',
|
||||
selection: '#3e4452',
|
||||
black: '#3f4451',
|
||||
red: '#e05561',
|
||||
green: '#8cc265',
|
||||
yellow: '#d18f52',
|
||||
blue: '#4aa5f0',
|
||||
magenta: '#c162de',
|
||||
cyan: '#42b3c2',
|
||||
white: '#d7dae0',
|
||||
brightBlack: '#4f5666',
|
||||
brightRed: '#ff616e',
|
||||
brightGreen: '#a5e075',
|
||||
brightYellow: '#f0a45d',
|
||||
brightBlue: '#4dc4ff',
|
||||
brightMagenta: '#de73ff',
|
||||
brightCyan: '#4cd1e0',
|
||||
brightWhite: '#e6e6e6',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'horizon-dark',
|
||||
name: 'Horizon',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#1c1e26',
|
||||
foreground: '#d5d8da',
|
||||
cursor: '#6c6f93',
|
||||
selection: '#6c6f93',
|
||||
black: '#16161c',
|
||||
red: '#e95678',
|
||||
green: '#29d398',
|
||||
yellow: '#fab795',
|
||||
blue: '#26bbd9',
|
||||
magenta: '#ee64ac',
|
||||
cyan: '#59e1e3',
|
||||
white: '#d5d8da',
|
||||
brightBlack: '#6c6f93',
|
||||
brightRed: '#ec6a88',
|
||||
brightGreen: '#3fdaa4',
|
||||
brightYellow: '#fbc3a7',
|
||||
brightBlue: '#3fc4de',
|
||||
brightMagenta: '#f075b5',
|
||||
brightCyan: '#6be4e6',
|
||||
brightWhite: '#ffffff',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'palenight',
|
||||
name: 'Palenight',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#292d3e',
|
||||
foreground: '#bfc7d5',
|
||||
cursor: '#ffcc00',
|
||||
selection: '#7580b8',
|
||||
black: '#292d3e',
|
||||
red: '#ff5572',
|
||||
green: '#a9c77d',
|
||||
yellow: '#ffcb6b',
|
||||
blue: '#82aaff',
|
||||
magenta: '#c792ea',
|
||||
cyan: '#89ddff',
|
||||
white: '#d0d0d0',
|
||||
brightBlack: '#676e95',
|
||||
brightRed: '#ff5572',
|
||||
brightGreen: '#c3e88d',
|
||||
brightYellow: '#ffcb6b',
|
||||
brightBlue: '#82aaff',
|
||||
brightMagenta: '#c792ea',
|
||||
brightCyan: '#89ddff',
|
||||
brightWhite: '#ffffff',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'panda',
|
||||
name: 'Panda',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#292a2b',
|
||||
foreground: '#e6e6e6',
|
||||
cursor: '#ff4b82',
|
||||
selection: '#454647',
|
||||
black: '#757575',
|
||||
red: '#ff2c6d',
|
||||
green: '#19f9d8',
|
||||
yellow: '#ffb86c',
|
||||
blue: '#45a9f9',
|
||||
magenta: '#ff75b5',
|
||||
cyan: '#b084eb',
|
||||
white: '#cdcdcd',
|
||||
brightBlack: '#757575',
|
||||
brightRed: '#ff2c6d',
|
||||
brightGreen: '#19f9d8',
|
||||
brightYellow: '#ffcc95',
|
||||
brightBlue: '#6fc1ff',
|
||||
brightMagenta: '#ff9ac1',
|
||||
brightCyan: '#bcaafe',
|
||||
brightWhite: '#e6e6e6',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'snazzy',
|
||||
name: 'Snazzy',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#1e1f29',
|
||||
foreground: '#ebece6',
|
||||
cursor: '#e4e4e4',
|
||||
selection: '#81aec6',
|
||||
black: '#000000',
|
||||
red: '#fc4346',
|
||||
green: '#50fb7c',
|
||||
yellow: '#f0fb8c',
|
||||
blue: '#49baff',
|
||||
magenta: '#fc4cb4',
|
||||
cyan: '#8be9fe',
|
||||
white: '#ededec',
|
||||
brightBlack: '#555555',
|
||||
brightRed: '#fc4346',
|
||||
brightGreen: '#50fb7c',
|
||||
brightYellow: '#f0fb8c',
|
||||
brightBlue: '#49baff',
|
||||
brightMagenta: '#fc4cb4',
|
||||
brightCyan: '#8be9fe',
|
||||
brightWhite: '#ededec',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'synthwave-84',
|
||||
name: "Synthwave '84",
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#262335',
|
||||
foreground: '#f0eff1',
|
||||
cursor: '#72f1b8',
|
||||
selection: '#463465',
|
||||
black: '#241b30',
|
||||
red: '#fe4450',
|
||||
green: '#72f1b8',
|
||||
yellow: '#fede5d',
|
||||
blue: '#03edf9',
|
||||
magenta: '#ff7edb',
|
||||
cyan: '#03edf9',
|
||||
white: '#f0eff1',
|
||||
brightBlack: '#7f7094',
|
||||
brightRed: '#fe4450',
|
||||
brightGreen: '#72f1b8',
|
||||
brightYellow: '#f9f972',
|
||||
brightBlue: '#aa54f9',
|
||||
brightMagenta: '#ff7edb',
|
||||
brightCyan: '#03edf9',
|
||||
brightWhite: '#f2f2e3',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'vesper',
|
||||
name: 'Vesper',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#101010',
|
||||
foreground: '#ffffff',
|
||||
cursor: '#acb1ab',
|
||||
selection: '#988049',
|
||||
black: '#101010',
|
||||
red: '#f5a191',
|
||||
green: '#90b99f',
|
||||
yellow: '#e6b99d',
|
||||
blue: '#aca1cf',
|
||||
magenta: '#e29eca',
|
||||
cyan: '#ea83a5',
|
||||
white: '#a0a0a0',
|
||||
brightBlack: '#7e7e7e',
|
||||
brightRed: '#ff8080',
|
||||
brightGreen: '#99ffe4',
|
||||
brightYellow: '#ffc799',
|
||||
brightBlue: '#b9aeda',
|
||||
brightMagenta: '#ecaad6',
|
||||
brightCyan: '#f591b2',
|
||||
brightWhite: '#ffffff',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'kanso-dark',
|
||||
name: 'Kanso Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#090e13',
|
||||
foreground: '#c5c9c7',
|
||||
cursor: '#c5c9c7',
|
||||
selection: '#393b44',
|
||||
black: '#0d0c0c',
|
||||
red: '#c4746e',
|
||||
green: '#8a9a7b',
|
||||
yellow: '#c4b28a',
|
||||
blue: '#8ba4b0',
|
||||
magenta: '#a292a3',
|
||||
cyan: '#8ea4a2',
|
||||
white: '#c8c093',
|
||||
brightBlack: '#a4a7a4',
|
||||
brightRed: '#e46876',
|
||||
brightGreen: '#87a987',
|
||||
brightYellow: '#e6c384',
|
||||
brightBlue: '#7fbbb3',
|
||||
brightMagenta: '#938aa9',
|
||||
brightCyan: '#7aa89f',
|
||||
brightWhite: '#c5c9c7',
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'kanso-light',
|
||||
name: 'Kanso Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#f2f1ef',
|
||||
foreground: '#22262d',
|
||||
cursor: '#22262d',
|
||||
selection: '#e2e1df',
|
||||
black: '#22262d',
|
||||
red: '#c84053',
|
||||
green: '#6f894e',
|
||||
yellow: '#77713f',
|
||||
blue: '#4d699b',
|
||||
magenta: '#b35b79',
|
||||
cyan: '#597b75',
|
||||
white: '#545464',
|
||||
brightBlack: '#6d6f6e',
|
||||
brightRed: '#d7474b',
|
||||
brightGreen: '#6e915f',
|
||||
brightYellow: '#836f4a',
|
||||
brightBlue: '#6693bf',
|
||||
brightMagenta: '#624c83',
|
||||
brightCyan: '#5e857a',
|
||||
brightWhite: '#43436c',
|
||||
}
|
||||
},
|
||||
];
|
||||
512
infrastructure/config/terminalThemes/modern.ts
Normal file
512
infrastructure/config/terminalThemes/modern.ts
Normal file
@@ -0,0 +1,512 @@
|
||||
import type { TerminalTheme } from '../../../domain/models';
|
||||
|
||||
export const modernTerminalThemes: TerminalTheme[] = [
|
||||
{ id: 'ui-pure-black', name: 'Pure Black', type: 'dark', colors: {
|
||||
background: '#000000', foreground: '#c9d1d9', cursor: '#58a6ff', selection: '#264f78',
|
||||
black: '#000000', red: '#ff7b72', green: '#3fb950', yellow: '#d29922',
|
||||
blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
||||
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
||||
}},
|
||||
{ id: 'ui-midnight', name: 'Midnight', type: 'dark', colors: {
|
||||
background: '#141822', foreground: '#c9d1d9', cursor: '#58a6ff', selection: '#264f78',
|
||||
black: '#141822', red: '#ff7b72', green: '#3fb950', yellow: '#d29922',
|
||||
blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
||||
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
||||
}},
|
||||
{ id: 'ui-deep-blue', name: 'Deep Blue', type: 'dark', colors: {
|
||||
background: '#111722', foreground: '#c9d1d9', cursor: '#58a6ff', selection: '#264f78',
|
||||
black: '#111722', red: '#ff7b72', green: '#3fb950', yellow: '#d29922',
|
||||
blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
||||
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
||||
}},
|
||||
{ id: 'ui-vscode', name: 'VS Code', type: 'dark', colors: {
|
||||
background: '#1f1f1f', foreground: '#cccccc', cursor: '#aeafad', selection: '#264f78',
|
||||
black: '#1f1f1f', red: '#f44747', green: '#6a9955', yellow: '#dcdcaa',
|
||||
blue: '#569cd6', magenta: '#c586c0', cyan: '#4ec9b0', white: '#d4d4d4',
|
||||
brightBlack: '#808080', brightRed: '#f44747', brightGreen: '#6a9955', brightYellow: '#dcdcaa',
|
||||
brightBlue: '#569cd6', brightMagenta: '#c586c0', brightCyan: '#4ec9b0', brightWhite: '#e5e5e5',
|
||||
}},
|
||||
{ id: 'ui-graphite', name: 'Graphite', type: 'dark', colors: {
|
||||
background: '#1c1e21', foreground: '#c9d1d9', cursor: '#58a6ff', selection: '#264f78',
|
||||
black: '#1c1e21', red: '#ff7b72', green: '#3fb950', yellow: '#d29922',
|
||||
blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
||||
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
||||
}},
|
||||
{ id: 'ui-obsidian', name: 'Obsidian', type: 'dark', colors: {
|
||||
background: '#131316', foreground: '#c9d1d9', cursor: '#a78bfa', selection: '#3b3175',
|
||||
black: '#131316', red: '#ff7b72', green: '#3fb950', yellow: '#d29922',
|
||||
blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
||||
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
||||
}},
|
||||
{ id: 'ui-forest', name: 'Forest', type: 'dark', colors: {
|
||||
background: '#161d1a', foreground: '#c9d1d9', cursor: '#34d399', selection: '#1a4a3a',
|
||||
black: '#161d1a', red: '#ff7b72', green: '#3fb950', yellow: '#d29922',
|
||||
blue: '#58a6ff', magenta: '#bc8cff', cyan: '#39c5cf', white: '#b1bac4',
|
||||
brightBlack: '#6e7681', brightRed: '#ffa198', brightGreen: '#56d364', brightYellow: '#e3b341',
|
||||
brightBlue: '#79c0ff', brightMagenta: '#d2a8ff', brightCyan: '#56d4dd', brightWhite: '#f0f6fc',
|
||||
}},
|
||||
{
|
||||
id: 'flexoki-dark',
|
||||
name: 'Flexoki Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#100F0F',
|
||||
foreground: '#CECDC3',
|
||||
cursor: '#CECDC3',
|
||||
selection: '#282726',
|
||||
black: '#100F0F',
|
||||
red: '#AF3029',
|
||||
green: '#66800B',
|
||||
yellow: '#AD8301',
|
||||
blue: '#205EA6',
|
||||
magenta: '#5E409D',
|
||||
cyan: '#24837B',
|
||||
white: '#CECDC3',
|
||||
brightBlack: '#282726',
|
||||
brightRed: '#D14D41',
|
||||
brightGreen: '#879A39',
|
||||
brightYellow: '#D0A215',
|
||||
brightBlue: '#4385BE',
|
||||
brightMagenta: '#8B7EC8',
|
||||
brightCyan: '#3AA99F',
|
||||
brightWhite: '#FFFCF0'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'kanagawa-wave',
|
||||
name: 'Kanagawa Wave',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#1F1F28',
|
||||
foreground: '#DCD7BA',
|
||||
cursor: '#C8C093',
|
||||
selection: '#2D4F67',
|
||||
black: '#090618',
|
||||
red: '#C34043',
|
||||
green: '#76946A',
|
||||
yellow: '#C0A36E',
|
||||
blue: '#7E9CD8',
|
||||
magenta: '#957FB8',
|
||||
cyan: '#6A9589',
|
||||
white: '#C8C093',
|
||||
brightBlack: '#727169',
|
||||
brightRed: '#E82424',
|
||||
brightGreen: '#98BB6C',
|
||||
brightYellow: '#E6C384',
|
||||
brightBlue: '#7FB4CA',
|
||||
brightMagenta: '#938AA9',
|
||||
brightCyan: '#7AA89F',
|
||||
brightWhite: '#DCD7BA'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'kanagawa-dragon',
|
||||
name: 'Kanagawa Dragon',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#181616',
|
||||
foreground: '#c5c9c5',
|
||||
cursor: '#c8c093',
|
||||
selection: '#2d4f67',
|
||||
black: '#0d0c0c',
|
||||
red: '#c4746e',
|
||||
green: '#8a9a7b',
|
||||
yellow: '#c4b28a',
|
||||
blue: '#8ba4b0',
|
||||
magenta: '#a292a3',
|
||||
cyan: '#8ea4a2',
|
||||
white: '#c5c9c5',
|
||||
brightBlack: '#a6a69c',
|
||||
brightRed: '#e46876',
|
||||
brightGreen: '#87a987',
|
||||
brightYellow: '#e6c384',
|
||||
brightBlue: '#7fb4ca',
|
||||
brightMagenta: '#938aa9',
|
||||
brightCyan: '#7aa89f',
|
||||
brightWhite: '#c5c9c5'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'hacker-green',
|
||||
name: 'Hacker Green',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0d0208',
|
||||
foreground: '#00ff41',
|
||||
cursor: '#00ff41',
|
||||
selection: '#003b00',
|
||||
black: '#000000',
|
||||
red: '#ff0000',
|
||||
green: '#00ff41',
|
||||
yellow: '#008F11',
|
||||
blue: '#005F00',
|
||||
magenta: '#00ff41',
|
||||
cyan: '#00ff41',
|
||||
white: '#00ff41',
|
||||
brightBlack: '#001100',
|
||||
brightRed: '#ff0000',
|
||||
brightGreen: '#00ff41',
|
||||
brightYellow: '#00ff41',
|
||||
brightBlue: '#00ff41',
|
||||
brightMagenta: '#00ff41',
|
||||
brightCyan: '#00ff41',
|
||||
brightWhite: '#ccffcc'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'hacker-blue',
|
||||
name: 'Hacker Blue',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#050a14',
|
||||
foreground: '#00aaff',
|
||||
cursor: '#00aaff',
|
||||
selection: '#002a4d',
|
||||
black: '#000000',
|
||||
red: '#ff3333',
|
||||
green: '#33ff33',
|
||||
yellow: '#ffff33',
|
||||
blue: '#00aaff',
|
||||
magenta: '#ff33ff',
|
||||
cyan: '#33ffff',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#333333',
|
||||
brightRed: '#ff6666',
|
||||
brightGreen: '#66ff66',
|
||||
brightYellow: '#ffff66',
|
||||
brightBlue: '#66ccff',
|
||||
brightMagenta: '#ff66ff',
|
||||
brightCyan: '#66ffff',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'night-owl',
|
||||
name: 'Night Owl',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#011627',
|
||||
foreground: '#d6deeb',
|
||||
cursor: '#80a4c2',
|
||||
selection: '#1d3b53',
|
||||
black: '#011627',
|
||||
red: '#ef5350',
|
||||
green: '#22da6e',
|
||||
yellow: '#addb67',
|
||||
blue: '#82aaff',
|
||||
magenta: '#c792ea',
|
||||
cyan: '#21c7a8',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#575656',
|
||||
brightRed: '#ef5350',
|
||||
brightGreen: '#22da6e',
|
||||
brightYellow: '#ffeb95',
|
||||
brightBlue: '#82aaff',
|
||||
brightMagenta: '#c792ea',
|
||||
brightCyan: '#7fdbca',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'aura',
|
||||
name: 'Aura',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#15141b',
|
||||
foreground: '#eae8f5',
|
||||
cursor: '#a594fd',
|
||||
selection: '#332e55',
|
||||
black: '#15141b',
|
||||
red: '#f72585',
|
||||
green: '#00d9ff',
|
||||
yellow: '#f7e025',
|
||||
blue: '#a594fd',
|
||||
magenta: '#f72585',
|
||||
cyan: '#00d9ff',
|
||||
white: '#eae8f5',
|
||||
brightBlack: '#433f5f',
|
||||
brightRed: '#f72585',
|
||||
brightGreen: '#00d9ff',
|
||||
brightYellow: '#f7e025',
|
||||
brightBlue: '#a594fd',
|
||||
brightMagenta: '#f72585',
|
||||
brightCyan: '#00d9ff',
|
||||
brightWhite: '#eae8f5'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rose-pine',
|
||||
name: 'Rose Pine',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#191724',
|
||||
foreground: '#e0def4',
|
||||
cursor: '#f6c6dc',
|
||||
selection: '#403d52',
|
||||
black: '#191724',
|
||||
red: '#eb6f92',
|
||||
green: '#31748f',
|
||||
yellow: '#f6c177',
|
||||
blue: '#9ccfd8',
|
||||
magenta: '#c4a7e7',
|
||||
cyan: '#ebbcba',
|
||||
white: '#e0def4',
|
||||
brightBlack: '#555169',
|
||||
brightRed: '#eb6f92',
|
||||
brightGreen: '#31748f',
|
||||
brightYellow: '#f6c177',
|
||||
brightBlue: '#9ccfd8',
|
||||
brightMagenta: '#c4a7e7',
|
||||
brightCyan: '#ebbcba',
|
||||
brightWhite: '#e0def4'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rose-pine-moon',
|
||||
name: 'Rose Pine Moon',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#232136',
|
||||
foreground: '#e0def4',
|
||||
cursor: '#f6c6dc',
|
||||
selection: '#44415a',
|
||||
black: '#232136',
|
||||
red: '#eb6f92',
|
||||
green: '#3e8fb0',
|
||||
yellow: '#f6c177',
|
||||
blue: '#9ccfd8',
|
||||
magenta: '#c4a7e7',
|
||||
cyan: '#ea9a97',
|
||||
white: '#e0def4',
|
||||
brightBlack: '#6e6a86',
|
||||
brightRed: '#eb6f92',
|
||||
brightGreen: '#3e8fb0',
|
||||
brightYellow: '#f6c177',
|
||||
brightBlue: '#9ccfd8',
|
||||
brightMagenta: '#c4a7e7',
|
||||
brightCyan: '#ea9a97',
|
||||
brightWhite: '#e0def4'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'rose-pine-dawn',
|
||||
name: 'Rose Pine Dawn',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#faf4ed',
|
||||
foreground: '#575279',
|
||||
cursor: '#d7828d',
|
||||
selection: '#f2e9e1',
|
||||
black: '#555169',
|
||||
red: '#b4637a',
|
||||
green: '#286983',
|
||||
yellow: '#d7af70',
|
||||
blue: '#56949f',
|
||||
magenta: '#907aa9',
|
||||
cyan: '#ea9d34',
|
||||
white: '#faf4ed',
|
||||
brightBlack: '#9893a5',
|
||||
brightRed: '#b4637a',
|
||||
brightGreen: '#286983',
|
||||
brightYellow: '#d7af70',
|
||||
brightBlue: '#56949f',
|
||||
brightMagenta: '#907aa9',
|
||||
brightCyan: '#ea9d34',
|
||||
brightWhite: '#575279'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'cobalt2',
|
||||
name: 'Cobalt2',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#132738',
|
||||
foreground: '#ffffff',
|
||||
cursor: '#ffffff',
|
||||
selection: '#1e3c41',
|
||||
black: '#000000',
|
||||
red: '#ff5d38',
|
||||
green: '#3ad900',
|
||||
yellow: '#ffb402',
|
||||
blue: '#0088ff',
|
||||
magenta: '#bc3fbc',
|
||||
cyan: '#00d0d0',
|
||||
white: '#d0d0d0',
|
||||
brightBlack: '#808080',
|
||||
brightRed: '#ff8070',
|
||||
brightGreen: '#66ff00',
|
||||
brightYellow: '#ffeb3b',
|
||||
brightBlue: '#5ba3ff',
|
||||
brightMagenta: '#ff4eff',
|
||||
brightCyan: '#5ce8e8',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'octocat-dark',
|
||||
name: 'Octocat Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0d1117',
|
||||
foreground: '#c9d1d9',
|
||||
cursor: '#58a6ff',
|
||||
selection: '#30363d',
|
||||
black: '#0d1117',
|
||||
red: '#f85149',
|
||||
green: '#3fb950',
|
||||
yellow: '#d29922',
|
||||
blue: '#58a6ff',
|
||||
magenta: '#bc8ef4',
|
||||
cyan: '#39c5cf',
|
||||
white: '#c9d1d9',
|
||||
brightBlack: '#30363d',
|
||||
brightRed: '#f85149',
|
||||
brightGreen: '#3fb950',
|
||||
brightYellow: '#d29922',
|
||||
brightBlue: '#58a6ff',
|
||||
brightMagenta: '#bc8ef4',
|
||||
brightCyan: '#39c5cf',
|
||||
brightWhite: '#c9d1d9'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'octocat-light',
|
||||
name: 'Octocat Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#ffffff',
|
||||
foreground: '#24292f',
|
||||
cursor: '#0969da',
|
||||
selection: '#e1e9f0',
|
||||
black: '#24292f',
|
||||
red: '#cf222e',
|
||||
green: '#1a7f37',
|
||||
yellow: '#9e6a03',
|
||||
blue: '#0969da',
|
||||
magenta: '#8250df',
|
||||
cyan: '#0884d9',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#57606a',
|
||||
brightRed: '#d1242f',
|
||||
brightGreen: '#238636',
|
||||
brightYellow: '#b08900',
|
||||
brightBlue: '#388bfd',
|
||||
brightMagenta: '#a371f7',
|
||||
brightCyan: '#56c2e6',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ayu-dark',
|
||||
name: 'Ayu Dark',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0f1419',
|
||||
foreground: '#e6e1cf',
|
||||
cursor: '#ffee99',
|
||||
selection: '#1f2430',
|
||||
black: '#000000',
|
||||
red: '#ff3333',
|
||||
green: '#86b300',
|
||||
yellow: '#ffb454',
|
||||
blue: '#36a3d9',
|
||||
magenta: '#f07178',
|
||||
cyan: '#95e1d3',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#323232',
|
||||
brightRed: '#ff6565',
|
||||
brightGreen: '#b8e536',
|
||||
brightYellow: '#ffc66d',
|
||||
brightBlue: '#55b4d4',
|
||||
brightMagenta: '#ff8888',
|
||||
brightCyan: '#95e1d3',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'ayu-light',
|
||||
name: 'Ayu Light',
|
||||
type: 'light',
|
||||
colors: {
|
||||
background: '#fafafa',
|
||||
foreground: '#5c6773',
|
||||
cursor: '#ffb454',
|
||||
selection: '#f0eee4',
|
||||
black: '#000000',
|
||||
red: '#ff3333',
|
||||
green: '#86b300',
|
||||
yellow: '#ffb454',
|
||||
blue: '#36a3d9',
|
||||
magenta: '#f07178',
|
||||
cyan: '#4dbf99',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#949494',
|
||||
brightRed: '#ff6565',
|
||||
brightGreen: '#b8e536',
|
||||
brightYellow: '#ffc66d',
|
||||
brightBlue: '#55b4d4',
|
||||
brightMagenta: '#ff8888',
|
||||
brightCyan: '#4dbf99',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'cyberpunk',
|
||||
name: 'Cyberpunk',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0d0221',
|
||||
foreground: '#ff006e',
|
||||
cursor: '#ffbe0b',
|
||||
selection: '#3a0ca3',
|
||||
black: '#0d0221',
|
||||
red: '#ff006e',
|
||||
green: '#8338ec',
|
||||
yellow: '#ffbe0b',
|
||||
blue: '#3a86ff',
|
||||
magenta: '#ff006e',
|
||||
cyan: '#06ffa5',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#6c5b7b',
|
||||
brightRed: '#ff006e',
|
||||
brightGreen: '#8338ec',
|
||||
brightYellow: '#ffbe0b',
|
||||
brightBlue: '#3a86ff',
|
||||
brightMagenta: '#ff006e',
|
||||
brightCyan: '#06ffa5',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'cyberpunk-scarlet',
|
||||
name: 'Cyberpunk Scarlet',
|
||||
type: 'dark',
|
||||
colors: {
|
||||
background: '#0d0221',
|
||||
foreground: '#ff0050',
|
||||
cursor: '#ff0050',
|
||||
selection: '#3a0ca3',
|
||||
black: '#0d0221',
|
||||
red: '#ff0050',
|
||||
green: '#ff0050',
|
||||
yellow: '#ff0050',
|
||||
blue: '#3a86ff',
|
||||
magenta: '#ff0050',
|
||||
cyan: '#06ffa5',
|
||||
white: '#ffffff',
|
||||
brightBlack: '#6c5b7b',
|
||||
brightRed: '#ff0050',
|
||||
brightGreen: '#ff0050',
|
||||
brightYellow: '#ff0050',
|
||||
brightBlue: '#3a86ff',
|
||||
brightMagenta: '#ff0050',
|
||||
brightCyan: '#06ffa5',
|
||||
brightWhite: '#ffffff'
|
||||
}
|
||||
},
|
||||
];
|
||||
2975
infrastructure/config/terminalThemes/systemPresets.ts
Normal file
2975
infrastructure/config/terminalThemes/systemPresets.ts
Normal file
File diff suppressed because it is too large
Load Diff
60
infrastructure/config/terminalThemes/uiMatch.ts
Normal file
60
infrastructure/config/terminalThemes/uiMatch.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { TerminalTheme } from '../../../domain/models';
|
||||
|
||||
export const uiMatchTerminalThemes: TerminalTheme[] = [
|
||||
// ====================================================================,
|
||||
// UI-matched terminal themes — background color matched to each built-in,
|
||||
// UI theme preset so the terminal blends seamlessly with the app chrome.,
|
||||
// ANSI palette based on netcatty-light (for light) / netcatty-dark (for dark).,
|
||||
// ====================================================================,
|
||||
// Light UI matches,
|
||||
{ id: 'ui-snow', name: 'Snow', type: 'light', colors: {
|
||||
background: '#f1f4f8', foreground: '#24292f', cursor: '#0969da', selection: '#add6ff',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
||||
blue: '#0969da', magenta: '#8250df', cyan: '#0e7574', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#0c7875', brightWhite: '#8c959f',
|
||||
}},
|
||||
{ id: 'ui-pure-white', name: 'Pure White', type: 'light', colors: {
|
||||
background: '#ffffff', foreground: '#24292f', cursor: '#0969da', selection: '#add6ff',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
||||
blue: '#0969da', magenta: '#8250df', cyan: '#0e7574', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#0c7875', brightWhite: '#8c959f',
|
||||
}},
|
||||
{ id: 'ui-ivory', name: 'Ivory', type: 'light', colors: {
|
||||
background: '#f7f4ed', foreground: '#24292f', cursor: '#b45309', selection: '#fde68a',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
||||
blue: '#0969da', magenta: '#8250df', cyan: '#0e7574', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#0c7875', brightWhite: '#8c959f',
|
||||
}},
|
||||
{ id: 'ui-mist', name: 'Mist', type: 'light', colors: {
|
||||
background: '#f6f7f9', foreground: '#24292f', cursor: '#0891b2', selection: '#a5f3fc',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
||||
blue: '#0969da', magenta: '#8250df', cyan: '#0e7574', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#0c7875', brightWhite: '#8c959f',
|
||||
}},
|
||||
{ id: 'ui-mint', name: 'Mint', type: 'light', colors: {
|
||||
background: '#f2f8f5', foreground: '#24292f', cursor: '#059669', selection: '#a7f3d0',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
||||
blue: '#0969da', magenta: '#8250df', cyan: '#0e7574', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#0c7875', brightWhite: '#8c959f',
|
||||
}},
|
||||
{ id: 'ui-sand', name: 'Sand', type: 'light', colors: {
|
||||
background: '#f4f0eb', foreground: '#24292f', cursor: '#c2410c', selection: '#fed7aa',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
||||
blue: '#0969da', magenta: '#8250df', cyan: '#0e7574', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#0c7875', brightWhite: '#8c959f',
|
||||
}},
|
||||
{ id: 'ui-lavender', name: 'Lavender', type: 'light', colors: {
|
||||
background: '#f7f5fa', foreground: '#24292f', cursor: '#7c3aed', selection: '#ddd6fe',
|
||||
black: '#24292f', red: '#cf222e', green: '#116329', yellow: '#9a6700',
|
||||
blue: '#0969da', magenta: '#8250df', cyan: '#0e7574', white: '#6e7781',
|
||||
brightBlack: '#57606a', brightRed: '#a40e26', brightGreen: '#1a7f37', brightYellow: '#7d4e00',
|
||||
brightBlue: '#218bff', brightMagenta: '#a475f9', brightCyan: '#0c7875', brightWhite: '#8c959f',
|
||||
}},
|
||||
// Dark UI matches,
|
||||
];
|
||||
35
infrastructure/config/uiFontBootstrap.test.ts
Normal file
35
infrastructure/config/uiFontBootstrap.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
/**
|
||||
* Regression for #2647: Windows boot CSS used a higher-specificity
|
||||
* `html.platform-win32 body { font-family: ... }` rule that beat
|
||||
* `body { font-family: var(--font-sans) }` from index.css. Popovers /
|
||||
* context menus inherit from body, so transfer / sync / quick-control
|
||||
* panels and menu items ignored the Appearance UI font setting.
|
||||
*/
|
||||
const indexHtml = readFileSync(new URL('../../index.html', import.meta.url), 'utf8');
|
||||
|
||||
describe('index.html UI font bootstrap', () => {
|
||||
it('applies the runtime --font-sans variable on body', () => {
|
||||
assert.match(
|
||||
indexHtml,
|
||||
/body\s*\{[^}]*font-family:\s*var\(--font-sans\)/s,
|
||||
'body must use var(--font-sans) so Appearance UI font updates reach portaled panels',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not hardcode a Windows body font stack that overrides --font-sans', () => {
|
||||
assert.doesNotMatch(
|
||||
indexHtml,
|
||||
/html\.platform-win32\s+body\s*\{[^}]*font-family:/s,
|
||||
'platform-win32 must not set body font-family; emoji fallbacks belong on --font-sans',
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
indexHtml,
|
||||
/body\s*\{[^}]*font-family:\s*['"]Space Grotesk['"]/s,
|
||||
'body must not hardcode Space Grotesk ahead of the user UI font setting',
|
||||
);
|
||||
});
|
||||
});
|
||||
82
infrastructure/config/uiFonts.test.ts
Normal file
82
infrastructure/config/uiFonts.test.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
DEFAULT_UI_FONT_ID,
|
||||
detectUiPlatform,
|
||||
UI_FONTS,
|
||||
withWindowsEmojiFallback,
|
||||
WINDOWS_FLAG_EMOJI_FONT,
|
||||
WINDOWS_UI_EMOJI_FONTS,
|
||||
withUiCjkFallback,
|
||||
} from './uiFonts';
|
||||
|
||||
describe('detectUiPlatform', () => {
|
||||
it('detects Windows user agents', () => {
|
||||
assert.equal(
|
||||
detectUiPlatform('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'),
|
||||
'win32',
|
||||
);
|
||||
});
|
||||
|
||||
it('detects macOS user agents', () => {
|
||||
assert.equal(
|
||||
detectUiPlatform('Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36'),
|
||||
'darwin',
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to linux for other platforms', () => {
|
||||
assert.equal(
|
||||
detectUiPlatform('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'),
|
||||
'linux',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('withWindowsEmojiFallback', () => {
|
||||
const sampleFamily = '"Space Grotesk", system-ui, sans-serif';
|
||||
|
||||
it('prepends bundled flag font and Windows emoji fonts for host labels', () => {
|
||||
const stack = withWindowsEmojiFallback(sampleFamily, 'win32');
|
||||
assert.ok(stack.startsWith(WINDOWS_UI_EMOJI_FONTS));
|
||||
assert.match(stack, /Noto Color Emoji Flags/);
|
||||
assert.match(stack, /Space Grotesk/);
|
||||
});
|
||||
|
||||
it('includes the bundled flag font before Segoe UI Emoji on Windows', () => {
|
||||
const stack = withWindowsEmojiFallback(sampleFamily, 'win32');
|
||||
const flagIndex = stack.indexOf(WINDOWS_FLAG_EMOJI_FONT);
|
||||
const segoeIndex = stack.indexOf('Segoe UI Emoji');
|
||||
assert.ok(flagIndex >= 0);
|
||||
assert.ok(segoeIndex >= 0);
|
||||
assert.ok(flagIndex < segoeIndex);
|
||||
});
|
||||
|
||||
it('keeps the stack unchanged on non-Windows platforms', () => {
|
||||
assert.equal(withWindowsEmojiFallback(sampleFamily, 'darwin'), sampleFamily);
|
||||
assert.equal(withWindowsEmojiFallback(sampleFamily, 'linux'), sampleFamily);
|
||||
});
|
||||
|
||||
it('does not double-prepend when emoji fonts are already present', () => {
|
||||
const alreadyPrefixed = `${WINDOWS_UI_EMOJI_FONTS}, ${sampleFamily}`;
|
||||
assert.equal(withWindowsEmojiFallback(alreadyPrefixed, 'win32'), alreadyPrefixed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('withUiCjkFallback', () => {
|
||||
it('still appends CJK fallbacks for UI font stacks', () => {
|
||||
const stack = withUiCjkFallback('"Space Grotesk", system-ui');
|
||||
assert.match(stack, /PingFang SC/);
|
||||
assert.match(stack, /Space Grotesk/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('default UI font', () => {
|
||||
it('uses Mona Sans while keeping Chinese fallbacks in the stack', () => {
|
||||
const defaultFont = UI_FONTS.find((font) => font.id === DEFAULT_UI_FONT_ID);
|
||||
assert.equal(DEFAULT_UI_FONT_ID, 'mona-sans');
|
||||
assert.equal(defaultFont?.name, 'Mona Sans');
|
||||
assert.match(defaultFont?.family ?? '', /Mona Sans/);
|
||||
assert.match(defaultFont?.family ?? '', /PingFang SC/);
|
||||
});
|
||||
});
|
||||
190
infrastructure/config/uiFonts.ts
Normal file
190
infrastructure/config/uiFonts.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* UI Fonts Configuration
|
||||
* Includes general-purpose fonts suitable for application interface
|
||||
*/
|
||||
|
||||
export interface UIFont {
|
||||
id: string;
|
||||
name: string;
|
||||
family: string;
|
||||
}
|
||||
|
||||
export type UiPlatform = 'darwin' | 'win32' | 'linux';
|
||||
|
||||
/**
|
||||
* Bundled Noto Color Emoji subset (regional indicators U+1F1E6–1F1FF).
|
||||
* Windows system fonts omit flag glyphs and show ISO country codes instead —
|
||||
* see #1589, #1604.
|
||||
*/
|
||||
export const WINDOWS_FLAG_EMOJI_FONT = '"Noto Color Emoji Flags"';
|
||||
|
||||
/**
|
||||
* Windows bundles UI fonts via @font-face. Their regional-indicator glyphs
|
||||
* render as separate letters instead of composed flag emoji unless a color
|
||||
* emoji font is consulted first — see #1589.
|
||||
*/
|
||||
export const WINDOWS_UI_EMOJI_FONTS = WINDOWS_FLAG_EMOJI_FONT;
|
||||
|
||||
/**
|
||||
* Fallback fonts for CJK (Chinese, Japanese, Korean) support
|
||||
*/
|
||||
const CJK_FALLBACK_FONTS = [
|
||||
'"PingFang SC"',
|
||||
'"Hiragino Sans GB"',
|
||||
'"Microsoft YaHei UI"',
|
||||
'"Microsoft YaHei"',
|
||||
'"Noto Sans CJK SC"',
|
||||
'"Source Han Sans SC"',
|
||||
'sans-serif',
|
||||
];
|
||||
|
||||
const CJK_FALLBACK_STACK = CJK_FALLBACK_FONTS.join(', ');
|
||||
|
||||
export const withUiCjkFallback = (family: string) => {
|
||||
const trimmed = family.trim();
|
||||
// Avoid double-appending if a custom stack already includes one of these fonts.
|
||||
if (CJK_FALLBACK_FONTS.some((f) => trimmed.includes(f.replace(/"/g, '')))) {
|
||||
return trimmed;
|
||||
}
|
||||
return `${trimmed}, ${CJK_FALLBACK_STACK}`;
|
||||
};
|
||||
|
||||
export function detectUiPlatform(userAgent: string): UiPlatform {
|
||||
if (/Win/i.test(userAgent)) return 'win32';
|
||||
if (/Mac|iPod|iPhone|iPad/i.test(userAgent)) return 'darwin';
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
export function withWindowsEmojiFallback(
|
||||
family: string,
|
||||
platform: UiPlatform = typeof navigator !== 'undefined'
|
||||
? detectUiPlatform(navigator.userAgent)
|
||||
: 'linux',
|
||||
): string {
|
||||
if (platform !== 'win32') return family;
|
||||
const trimmed = family.trim();
|
||||
if (trimmed.includes('Noto Color Emoji Flags') || trimmed.includes('Segoe UI Emoji')) {
|
||||
return trimmed;
|
||||
}
|
||||
return `${WINDOWS_UI_EMOJI_FONTS}, ${trimmed}, "Segoe UI Emoji", "Segoe UI Symbol"`;
|
||||
}
|
||||
|
||||
const BASE_UI_FONTS: UIFont[] = [
|
||||
{
|
||||
id: 'mona-sans',
|
||||
name: 'Mona Sans',
|
||||
family: '"Mona Sans", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'space-grotesk',
|
||||
name: 'Space Grotesk',
|
||||
family: '"Space Grotesk", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'system-ui',
|
||||
name: 'System UI',
|
||||
family: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial',
|
||||
},
|
||||
{
|
||||
id: 'inter',
|
||||
name: 'Inter',
|
||||
family: '"Inter", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'roboto',
|
||||
name: 'Roboto',
|
||||
family: '"Roboto", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'open-sans',
|
||||
name: 'Open Sans',
|
||||
family: '"Open Sans", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'lato',
|
||||
name: 'Lato',
|
||||
family: '"Lato", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'nunito',
|
||||
name: 'Nunito',
|
||||
family: '"Nunito", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'poppins',
|
||||
name: 'Poppins',
|
||||
family: '"Poppins", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'source-sans-pro',
|
||||
name: 'Source Sans Pro',
|
||||
family: '"Source Sans Pro", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'ubuntu',
|
||||
name: 'Ubuntu',
|
||||
family: '"Ubuntu", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'noto-sans',
|
||||
name: 'Noto Sans',
|
||||
family: '"Noto Sans", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'work-sans',
|
||||
name: 'Work Sans',
|
||||
family: '"Work Sans", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'dm-sans',
|
||||
name: 'DM Sans',
|
||||
family: '"DM Sans", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'montserrat',
|
||||
name: 'Montserrat',
|
||||
family: '"Montserrat", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'raleway',
|
||||
name: 'Raleway',
|
||||
family: '"Raleway", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'quicksand',
|
||||
name: 'Quicksand',
|
||||
family: '"Quicksand", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'ibm-plex-sans',
|
||||
name: 'IBM Plex Sans',
|
||||
family: '"IBM Plex Sans", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'outfit',
|
||||
name: 'Outfit',
|
||||
family: '"Outfit", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'plus-jakarta-sans',
|
||||
name: 'Plus Jakarta Sans',
|
||||
family: '"Plus Jakarta Sans", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'segoe-ui',
|
||||
name: 'Segoe UI',
|
||||
family: '"Segoe UI", system-ui',
|
||||
},
|
||||
{
|
||||
id: 'sf-pro',
|
||||
name: 'SF Pro',
|
||||
family: '-apple-system, BlinkMacSystemFont, "SF Pro Display", system-ui',
|
||||
},
|
||||
];
|
||||
|
||||
export const UI_FONTS: UIFont[] = BASE_UI_FONTS.map((font) => ({
|
||||
...font,
|
||||
family: withUiCjkFallback(font.family),
|
||||
}));
|
||||
|
||||
export const DEFAULT_UI_FONT_ID = 'mona-sans';
|
||||
111
infrastructure/config/uiThemes.test.ts
Normal file
111
infrastructure/config/uiThemes.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
DARK_UI_THEMES,
|
||||
EXTENDED_DARK_UI_THEMES,
|
||||
EXTENDED_LIGHT_UI_THEMES,
|
||||
LIGHT_UI_THEMES,
|
||||
getUiThemeById,
|
||||
} from "./uiThemes";
|
||||
import { TERMINAL_THEMES } from "./terminalThemes";
|
||||
|
||||
const SYSTEM_PRESET_THEME_IDS = [
|
||||
"a-cup-of-coffee",
|
||||
"abolkog",
|
||||
"aurora",
|
||||
"ayu",
|
||||
"base16-flat",
|
||||
"base16-mocha",
|
||||
"blue-dolphin",
|
||||
"calm-days-sober-nights-sky",
|
||||
"catppuccin",
|
||||
"chai",
|
||||
"chinolor",
|
||||
"cyberdyne",
|
||||
"desert",
|
||||
"django-reborn-again",
|
||||
"espresso",
|
||||
"eyehealth",
|
||||
"flexoki",
|
||||
"fox",
|
||||
"garbage-oracle",
|
||||
"github",
|
||||
"gruvbox-material",
|
||||
"homebrew",
|
||||
"ic-orange-ppl",
|
||||
"ikki",
|
||||
"kanso-ink",
|
||||
"kary-pro-colors",
|
||||
"light-purple",
|
||||
"mondrian",
|
||||
"monochrome",
|
||||
"monochrome-stone",
|
||||
"monokai-pro-spectrum",
|
||||
"monospace",
|
||||
"noctis-azureus",
|
||||
"noctis-hibernus",
|
||||
"noir-essence",
|
||||
"nord-midnight",
|
||||
"notionish",
|
||||
"phonebook",
|
||||
"polychrome",
|
||||
"purplepeter",
|
||||
"rainglow-codecourse",
|
||||
"rainglow-crisp",
|
||||
"rainglow-lavender",
|
||||
"remedy-tilted",
|
||||
"rose-pine",
|
||||
"selene-selenized",
|
||||
"soft-color",
|
||||
"tearout",
|
||||
"tokyo-night",
|
||||
"tomorrow-night-eighties",
|
||||
"vaporizer-turquoise",
|
||||
"xotopio",
|
||||
"yuttari",
|
||||
"zenbones-rosebones",
|
||||
"zhxo-red",
|
||||
];
|
||||
|
||||
describe("system preset UI themes", () => {
|
||||
it("adds every imported preset at the same level as the existing UI themes", () => {
|
||||
assert.deepEqual(EXTENDED_LIGHT_UI_THEMES.map((theme) => theme.id), SYSTEM_PRESET_THEME_IDS);
|
||||
assert.deepEqual(EXTENDED_DARK_UI_THEMES.map((theme) => theme.id), SYSTEM_PRESET_THEME_IDS);
|
||||
|
||||
for (const id of SYSTEM_PRESET_THEME_IDS) {
|
||||
assert.equal(getUiThemeById("light", id).id, id);
|
||||
assert.equal(getUiThemeById("dark", id).id, id);
|
||||
}
|
||||
|
||||
assert.equal(EXTENDED_LIGHT_UI_THEMES.length, 55);
|
||||
assert.equal(EXTENDED_DARK_UI_THEMES.length, 55);
|
||||
assert.equal(LIGHT_UI_THEMES.length, 7 + 55);
|
||||
assert.equal(DARK_UI_THEMES.length, 7 + 55);
|
||||
assert.equal([...LIGHT_UI_THEMES, ...DARK_UI_THEMES].filter((theme) => theme.collection !== undefined && theme.collection !== "core").length, 0);
|
||||
});
|
||||
|
||||
it("keeps theme accents distinct from the original default blue", () => {
|
||||
const originalDefaultBlue = "221.2 83.2% 53.3%";
|
||||
const accents = new Set([
|
||||
...EXTENDED_LIGHT_UI_THEMES.map((theme) => theme.tokens.accent),
|
||||
...EXTENDED_DARK_UI_THEMES.map((theme) => theme.tokens.accent),
|
||||
]);
|
||||
|
||||
assert.ok(accents.size > 20);
|
||||
assert.ok(!accents.has(originalDefaultBlue));
|
||||
});
|
||||
|
||||
it("adds matching terminal themes for every imported light and dark UI preset", () => {
|
||||
const terminalThemeIds = new Set(TERMINAL_THEMES.map((theme) => theme.id));
|
||||
assert.equal(terminalThemeIds.size, TERMINAL_THEMES.length);
|
||||
|
||||
for (const id of SYSTEM_PRESET_THEME_IDS) {
|
||||
const lightTerminalTheme = TERMINAL_THEMES.find((theme) => theme.id === `system-${id}-light`);
|
||||
const darkTerminalTheme = TERMINAL_THEMES.find((theme) => theme.id === `system-${id}-dark`);
|
||||
|
||||
assert.equal(lightTerminalTheme?.type, "light", id);
|
||||
assert.equal(darkTerminalTheme?.type, "dark", id);
|
||||
}
|
||||
});
|
||||
});
|
||||
3163
infrastructure/config/uiThemes.ts
Normal file
3163
infrastructure/config/uiThemes.ts
Normal file
File diff suppressed because it is too large
Load Diff
17
infrastructure/config/xtermPerformance.test.ts
Normal file
17
infrastructure/config/xtermPerformance.test.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import {
|
||||
XTERM_UNLIMITED_SCROLLBACK_CAP,
|
||||
resolveXTermScrollback,
|
||||
} from './xtermPerformance';
|
||||
|
||||
test('resolveXTermScrollback maps the unlimited sentinel to a 50000 row cap', () => {
|
||||
assert.equal(XTERM_UNLIMITED_SCROLLBACK_CAP, 50000);
|
||||
assert.equal(resolveXTermScrollback(0), 50000);
|
||||
});
|
||||
|
||||
test('resolveXTermScrollback preserves explicit positive scrollback values', () => {
|
||||
assert.equal(resolveXTermScrollback(10000), 10000);
|
||||
assert.equal(resolveXTermScrollback(50000), 50000);
|
||||
});
|
||||
200
infrastructure/config/xtermPerformance.ts
Normal file
200
infrastructure/config/xtermPerformance.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* XTerm.js Performance Optimization Configuration
|
||||
*
|
||||
* This file contains platform-specific optimizations for xterm performance.
|
||||
* macOS has different performance characteristics than Windows due to:
|
||||
* - Stricter GPU memory management
|
||||
* - Different rendering pipeline (Metal vs DirectX)
|
||||
* - Memory pressure handling
|
||||
*/
|
||||
|
||||
export const XTERM_UNLIMITED_SCROLLBACK_CAP = 50000;
|
||||
|
||||
export function resolveXTermScrollback(scrollback: number): number {
|
||||
// xterm.js treats 0 as "no scrollback". Keep the app's 0 sentinel useful
|
||||
// without asking xterm to resize/reflow nearly one million buffer rows.
|
||||
return scrollback === 0 ? XTERM_UNLIMITED_SCROLLBACK_CAP : scrollback;
|
||||
}
|
||||
|
||||
export const XTERM_PERFORMANCE_CONFIG = {
|
||||
// Memory and Scrollback Settings
|
||||
scrollback: {
|
||||
// Windows can handle larger buffers efficiently
|
||||
default: 3000,
|
||||
// macOS performance degrades with large scrollbacks
|
||||
// due to more aggressive memory pressure
|
||||
macOS: 1000,
|
||||
// Mobile-like environments
|
||||
lowMemory: 500,
|
||||
},
|
||||
|
||||
// Rendering optimizations
|
||||
rendering: {
|
||||
// Disable cursor blinking - reduces render calls significantly
|
||||
cursorBlink: false,
|
||||
|
||||
// Allow transparency is expensive on macOS with Metal
|
||||
// Disabling it improves performance by 15-20%
|
||||
allowTransparency: false,
|
||||
|
||||
// Custom glyphs: xterm.js draws box/block characters on canvas
|
||||
// instead of using font glyphs, eliminating gaps between cells
|
||||
customGlyphs: true,
|
||||
|
||||
// Font rendering settings
|
||||
letterSpacing: 0,
|
||||
lineHeight: 1,
|
||||
|
||||
// Keep viewport movement smooth without feeling sluggish.
|
||||
smoothScrollDuration: 120,
|
||||
},
|
||||
|
||||
// WebGL-specific optimizations
|
||||
webgl: {
|
||||
// Enable WebGL by default for GPU acceleration
|
||||
enabled: true,
|
||||
|
||||
// User can choose DOM renderer on any platform (canvas removed in xterm 6.0)
|
||||
preferDOM: false,
|
||||
|
||||
// Handle WebGL context loss gracefully
|
||||
enableContextLoss: true,
|
||||
},
|
||||
|
||||
// Event handling optimizations
|
||||
events: {
|
||||
// Use document override for better event routing on macOS
|
||||
documentOverride: true,
|
||||
|
||||
// Standard tab width (8 spaces)
|
||||
tabStopWidth: 8,
|
||||
|
||||
// Let the SSH daemon handle EOL conversion
|
||||
convertEol: false,
|
||||
|
||||
// Allow bracketed paste mode for better paste handling
|
||||
ignoreBracketedPasteMode: false,
|
||||
},
|
||||
|
||||
// Logging (disable in production for performance)
|
||||
logging: {
|
||||
logLevel: 'off' as const, // 'off' | 'error' | 'warn' | 'info' | 'debug'
|
||||
},
|
||||
|
||||
// Resize debouncing (macOS can get flooded with resize events)
|
||||
resize: {
|
||||
// Debounce delay in milliseconds
|
||||
// Higher values reduce CPU usage but may feel less responsive
|
||||
debounceMs: 50,
|
||||
|
||||
// Use requestAnimationFrame for resize fitting
|
||||
useRAF: true,
|
||||
},
|
||||
|
||||
// Performance monitoring thresholds
|
||||
monitoring: {
|
||||
// Log performance warning if render takes longer than this (ms)
|
||||
slowRenderThreshold: 16, // 60fps = 16.67ms per frame
|
||||
|
||||
// Log warning if data buffer gets too large
|
||||
largeBufferThreshold: 1024 * 1024, // 1MB
|
||||
},
|
||||
|
||||
// Output-pressure quiet window shared by optional terminal side work.
|
||||
highlighting: {
|
||||
// Let large output settle before deferred work catches up.
|
||||
largeOutputQuietMs: 480,
|
||||
},
|
||||
};
|
||||
|
||||
export type XTermPlatform = "darwin" | "win32" | "linux";
|
||||
|
||||
type RendererType = "dom";
|
||||
type LogLevel = "off" | "error" | "warn" | "info" | "debug";
|
||||
|
||||
export type ResolvedXTermPerformance = {
|
||||
options: {
|
||||
scrollback: number;
|
||||
cursorBlink: boolean;
|
||||
allowTransparency: boolean;
|
||||
customGlyphs: boolean;
|
||||
letterSpacing: number;
|
||||
lineHeight: number;
|
||||
smoothScrollDuration: number;
|
||||
documentOverride: boolean;
|
||||
tabStopWidth: number;
|
||||
convertEol: boolean;
|
||||
ignoreBracketedPasteMode: boolean;
|
||||
logLevel: LogLevel;
|
||||
rendererType?: RendererType;
|
||||
};
|
||||
useWebGLAddon: boolean;
|
||||
preferDOMRenderer: boolean;
|
||||
};
|
||||
|
||||
const isLowMemoryDevice = (deviceMemoryGb?: number) =>
|
||||
typeof deviceMemoryGb === "number" && deviceMemoryGb > 0 && deviceMemoryGb <= 4;
|
||||
|
||||
export type RendererPreference = "auto" | "webgl" | "dom";
|
||||
|
||||
/**
|
||||
* Resolve a platform and hardware aware performance profile.
|
||||
* When rendererType is 'auto', uses DOM on low-memory devices to avoid WebGL overhead.
|
||||
*/
|
||||
export function resolveXTermPerformanceConfig({
|
||||
platform = "darwin",
|
||||
deviceMemoryGb,
|
||||
rendererType = "auto",
|
||||
}: {
|
||||
platform?: XTermPlatform;
|
||||
deviceMemoryGb?: number;
|
||||
rendererType?: RendererPreference;
|
||||
} = {}): ResolvedXTermPerformance {
|
||||
const baseConfig = XTERM_PERFORMANCE_CONFIG;
|
||||
|
||||
const lowMem = isLowMemoryDevice(deviceMemoryGb);
|
||||
|
||||
// Determine if we should use DOM renderer (canvas removed in xterm 6.0)
|
||||
let resolvedPreferDOM: boolean;
|
||||
if (rendererType === "dom") {
|
||||
resolvedPreferDOM = true;
|
||||
} else if (rendererType === "webgl") {
|
||||
resolvedPreferDOM = false;
|
||||
} else {
|
||||
// Auto mode: use DOM on low-memory devices
|
||||
resolvedPreferDOM = baseConfig.webgl.preferDOM || lowMem;
|
||||
}
|
||||
|
||||
const scrollbackProfile = lowMem
|
||||
? "lowMemory"
|
||||
: platform === "darwin"
|
||||
? "macOS"
|
||||
: "default";
|
||||
|
||||
const resolvedRendererType = resolvedPreferDOM ? ("dom" as const) : undefined;
|
||||
|
||||
const baseOptions = {
|
||||
scrollback: baseConfig.scrollback[scrollbackProfile],
|
||||
cursorBlink: baseConfig.rendering.cursorBlink,
|
||||
allowTransparency: baseConfig.rendering.allowTransparency,
|
||||
customGlyphs: baseConfig.rendering.customGlyphs,
|
||||
letterSpacing: baseConfig.rendering.letterSpacing,
|
||||
lineHeight: baseConfig.rendering.lineHeight,
|
||||
smoothScrollDuration: baseConfig.rendering.smoothScrollDuration,
|
||||
documentOverride: baseConfig.events.documentOverride,
|
||||
tabStopWidth: baseConfig.events.tabStopWidth,
|
||||
convertEol: baseConfig.events.convertEol,
|
||||
ignoreBracketedPasteMode: baseConfig.events.ignoreBracketedPasteMode,
|
||||
logLevel: baseConfig.logging.logLevel,
|
||||
};
|
||||
|
||||
const options = resolvedRendererType
|
||||
? { ...baseOptions, rendererType: resolvedRendererType }
|
||||
: baseOptions;
|
||||
|
||||
return {
|
||||
options,
|
||||
useWebGLAddon: baseConfig.webgl.enabled && !resolvedPreferDOM,
|
||||
preferDOMRenderer: resolvedPreferDOM,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user