diff --git a/apps/backend/internal/agent/lifecycle/manager.go b/apps/backend/internal/agent/lifecycle/manager.go index 6487e7d034..7bae245bf2 100644 --- a/apps/backend/internal/agent/lifecycle/manager.go +++ b/apps/backend/internal/agent/lifecycle/manager.go @@ -896,6 +896,13 @@ func (m *Manager) StartAgentProcess(ctx context.Context, executionID string) err } } + m.logger.Warn("StartAgentProcess: task description resolved", + zap.String("execution_id", executionID), + zap.String("task_id", execution.TaskID), + zap.Int("task_description_length", len(taskDescription)), + zap.String("agent_command", execution.AgentCommand), + zap.String("acp_session_id", execution.ACPSessionID)) + // Build environment for the agent process env := map[string]string{} if taskDescription != "" { @@ -1376,7 +1383,13 @@ func (m *Manager) handleAgentEvent(execution *AgentExecution, event agentctl.Age } m.logger.Warn("error completion received, marking execution as failed", zap.String("execution_id", execution.ID), - zap.String("error", errorMsg)) + zap.String("task_id", execution.TaskID), + zap.String("error", errorMsg), + zap.String("event_error", event.Error), + zap.String("event_text", event.Text), + zap.Any("event_data", event.Data), + zap.String("agent_command", execution.AgentCommand), + zap.String("acp_session_id", execution.ACPSessionID)) if err := m.MarkCompleted(execution.ID, 1, errorMsg); err != nil { m.logger.Error("failed to mark execution as failed after error completion", diff --git a/apps/backend/internal/agentctl/client/agent.go b/apps/backend/internal/agentctl/client/agent.go index c20b40a80a..421195a9e4 100644 --- a/apps/backend/internal/agentctl/client/agent.go +++ b/apps/backend/internal/agentctl/client/agent.go @@ -249,6 +249,10 @@ func (c *Client) Prompt(ctx context.Context, text string, attachments []v1.Messa return nil, fmt.Errorf("failed to parse prompt response (status %d, body: %s): %w", resp.StatusCode, truncateBody(respBody), err) } if !result.Success { + c.logger.Warn("prompt returned failure response", + zap.String("error", result.Error), + zap.Int("status_code", resp.StatusCode), + zap.String("response_body", truncateBody(respBody))) return nil, fmt.Errorf("prompt failed: %s", result.Error) } return &result, nil diff --git a/apps/backend/internal/agentctl/server/adapter/transport/streamjson/adapter.go b/apps/backend/internal/agentctl/server/adapter/transport/streamjson/adapter.go index 118e9bf113..3816b6ab1e 100644 --- a/apps/backend/internal/agentctl/server/adapter/transport/streamjson/adapter.go +++ b/apps/backend/internal/agentctl/server/adapter/transport/streamjson/adapter.go @@ -1120,19 +1120,30 @@ func (a *Adapter) handleResultMessage(msg *claudecode.CLIMessage, sessionID, ope a.streamingTextSentThisTurn = false a.mu.Unlock() + // Build error message from errors array if available + var errorMsg string + if msg.IsError && len(msg.Errors) > 0 { + errorMsg = strings.Join(msg.Errors, "; ") + } + // Send completion event AFTER any result text chunk + completeData := map[string]any{ + "cost_usd": msg.CostUSD, + "duration_ms": msg.DurationMS, + "num_turns": msg.NumTurns, + "input_tokens": msg.TotalInputTokens, + "output_tokens": msg.TotalOutputTokens, + "is_error": msg.IsError, + } + if len(msg.Errors) > 0 { + completeData["errors"] = msg.Errors + } a.sendUpdate(AgentEvent{ Type: streams.EventTypeComplete, SessionID: sessionID, OperationID: operationID, - Data: map[string]any{ - "cost_usd": msg.CostUSD, - "duration_ms": msg.DurationMS, - "num_turns": msg.NumTurns, - "input_tokens": msg.TotalInputTokens, - "output_tokens": msg.TotalOutputTokens, - "is_error": msg.IsError, - }, + Error: errorMsg, + Data: completeData, }) // Signal completion @@ -1142,7 +1153,7 @@ func (a *Adapter) handleResultMessage(msg *claudecode.CLIMessage, sessionID, ope if resultCh != nil { select { - case resultCh <- resultComplete{success: !msg.IsError}: + case resultCh <- resultComplete{success: !msg.IsError, err: errorMsg}: a.logger.Debug("signaled prompt completion") default: a.logger.Warn("result channel full, dropping signal") @@ -1151,18 +1162,20 @@ func (a *Adapter) handleResultMessage(msg *claudecode.CLIMessage, sessionID, ope // Send error event if failed if msg.IsError { - errorMsg := "prompt failed" - // Error result can be a string (API error) or ResultData with Text - if errStr := msg.GetResultString(); errStr != "" { - errorMsg = errStr + errMsg := "prompt failed" + // Use errors array first (most specific), then fallback to result string/data + if len(msg.Errors) > 0 { + errMsg = strings.Join(msg.Errors, "; ") + } else if errStr := msg.GetResultString(); errStr != "" { + errMsg = errStr } else if resultData := msg.GetResultData(); resultData != nil && resultData.Text != "" { - errorMsg = resultData.Text + errMsg = resultData.Text } a.sendUpdate(AgentEvent{ Type: streams.EventTypeError, SessionID: sessionID, OperationID: operationID, - Error: errorMsg, + Error: errMsg, }) } } diff --git a/apps/backend/internal/orchestrator/event_handlers.go b/apps/backend/internal/orchestrator/event_handlers.go index 74a6cd9180..67e34678fa 100644 --- a/apps/backend/internal/orchestrator/event_handlers.go +++ b/apps/backend/internal/orchestrator/event_handlers.go @@ -3,6 +3,8 @@ package orchestrator import ( "context" + "fmt" + "strings" "time" "go.uber.org/zap" @@ -468,6 +470,16 @@ func (s *Service) handleAgentFailed(ctx context.Context, data watcher.AgentEvent zap.String("agent_execution_id", data.AgentExecutionID), zap.String("error_message", data.ErrorMessage)) + // Check if this was a resume failure (agent session no longer exists on the CLI side). + // If so, clear the resume token and re-launch a fresh session instead of failing. + // We do this BEFORE setting the session state to FAILED so the re-launch can proceed cleanly. + if data.SessionID != "" && isResumeFailure(data.ErrorMessage) { + if s.handleResumeFailure(ctx, data) { + return // Successfully re-launched without resume + } + // Fall through to normal failure handling if re-launch failed + } + // Update session state to FAILED if data.SessionID != "" { s.updateTaskSessionState(ctx, data.TaskID, data.SessionID, models.TaskSessionStateFailed, data.ErrorMessage, false) @@ -500,6 +512,103 @@ func (s *Service) handleAgentFailed(ctx context.Context, data watcher.AgentEvent } } +// isResumeFailure checks if the error message indicates a failed --resume attempt +// (e.g., the Claude Code conversation was deleted or expired). +func isResumeFailure(errorMsg string) bool { + lower := strings.ToLower(errorMsg) + return strings.Contains(lower, "no conversation found") +} + +// handleResumeFailure handles the case where an agent failed because --resume pointed +// to a non-existent conversation. It clears the resume token, sends a status message, +// and schedules an asynchronous re-launch of the session without resume. +// +// The re-launch must be async because this handler is called synchronously from +// MarkCompleted → PublishAgentEvent, BEFORE RemoveExecution cleans up the failed +// execution from the manager's store. Calling ResumeSession synchronously would +// fail with ErrExecutionAlreadyRunning. +// +// Returns true to signal that the caller should skip normal failure handling +// (scheduler retry, FAILED state) since we're handling the retry ourselves. +func (s *Service) handleResumeFailure(ctx context.Context, data watcher.AgentEventData) bool { + s.logger.Warn("detected resume failure, retrying without resume", + zap.String("task_id", data.TaskID), + zap.String("session_id", data.SessionID), + zap.String("error", data.ErrorMessage)) + + // 1. Clear the resume token so the next attempt won't use --resume. + // Note: executor.ResumeSession already upserts a new ExecutorRunning without ResumeToken, + // but clear it explicitly to be safe against race conditions. + running, err := s.repo.GetExecutorRunningBySessionID(ctx, data.SessionID) + if err == nil && running != nil && running.ResumeToken != "" { + running.ResumeToken = "" + if upsertErr := s.repo.UpsertExecutorRunning(ctx, running); upsertErr != nil { + s.logger.Error("failed to clear resume token", + zap.String("session_id", data.SessionID), + zap.Error(upsertErr)) + } + } + + // 2. Send a status message about the failed resume + if s.messageCreator != nil { + statusMsg := fmt.Sprintf("Session resume failed: %s. Starting a fresh session...", data.ErrorMessage) + if err := s.messageCreator.CreateSessionMessage( + ctx, + data.TaskID, + statusMsg, + data.SessionID, + string(v1.MessageTypeStatus), + s.getActiveTurnID(data.SessionID), + map[string]interface{}{ + "variant": "warning", + "resume_failed": true, + }, + false, + ); err != nil { + s.logger.Warn("failed to create resume failure status message", + zap.String("task_id", data.TaskID), + zap.Error(err)) + } + } + + // 3. Schedule async re-launch. We use a goroutine because this handler runs + // synchronously inside the MarkCompleted → PublishAgentEvent call chain, + // before RemoveExecution clears the old execution from the manager's store. + // A synchronous ResumeSession call would fail with ErrExecutionAlreadyRunning. + taskID := data.TaskID + sessionID := data.SessionID + go func() { + // Brief delay to let RemoveExecution complete after event handler returns + time.Sleep(500 * time.Millisecond) + bgCtx := context.Background() + + session, err := s.repo.GetTaskSession(bgCtx, sessionID) + if err != nil { + s.logger.Error("failed to get session for resume retry", + zap.String("session_id", sessionID), + zap.Error(err)) + _ = s.repo.UpdateTaskSessionState(bgCtx, sessionID, models.TaskSessionStateFailed, "re-launch failed: "+err.Error()) + return + } + + _, err = s.executor.ResumeSession(bgCtx, session, true) + if err != nil { + s.logger.Error("failed to re-launch session after resume failure", + zap.String("task_id", taskID), + zap.String("session_id", sessionID), + zap.Error(err)) + _ = s.repo.UpdateTaskSessionState(bgCtx, sessionID, models.TaskSessionStateFailed, "re-launch failed: "+err.Error()) + return + } + + s.logger.Info("session re-launched after resume failure (fresh session without --resume)", + zap.String("task_id", taskID), + zap.String("session_id", sessionID)) + }() + + return true +} + // handleAgentStopped handles agent stopped events (manual stop or cancellation) func (s *Service) handleAgentStopped(ctx context.Context, data watcher.AgentEventData) { s.logger.Info("handling agent stopped", diff --git a/apps/backend/internal/orchestrator/event_handlers_test.go b/apps/backend/internal/orchestrator/event_handlers_test.go new file mode 100644 index 0000000000..34ae42f271 --- /dev/null +++ b/apps/backend/internal/orchestrator/event_handlers_test.go @@ -0,0 +1,51 @@ +package orchestrator + +import "testing" + +func TestIsResumeFailure(t *testing.T) { + tests := []struct { + name string + errorMsg string + want bool + }{ + { + name: "exact match", + errorMsg: "no conversation found", + want: true, + }, + { + name: "mixed case", + errorMsg: "No Conversation Found", + want: true, + }, + { + name: "embedded in longer message", + errorMsg: "prompt failed: no conversation found for session abc-123", + want: true, + }, + { + name: "unrelated error", + errorMsg: "connection refused", + want: false, + }, + { + name: "empty string", + errorMsg: "", + want: false, + }, + { + name: "agent crashed", + errorMsg: "agent process exited with code 1", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := isResumeFailure(tt.errorMsg) + if got != tt.want { + t.Errorf("isResumeFailure(%q) = %v, want %v", tt.errorMsg, got, tt.want) + } + }) + } +} diff --git a/apps/backend/internal/orchestrator/executor/executor.go b/apps/backend/internal/orchestrator/executor/executor.go index f7e085a57d..cd2353b210 100644 --- a/apps/backend/internal/orchestrator/executor/executor.go +++ b/apps/backend/internal/orchestrator/executor/executor.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "strings" + "sync" "time" "github.com/google/uuid" @@ -158,6 +159,11 @@ type Executor struct { retryLimit int retryDelay time.Duration worktreeEnabled bool // Whether to use Git worktrees for agent isolation + + // Per-session locks to prevent concurrent resume/launch operations on the same session. + // This prevents race conditions when the backend restarts and multiple resume requests + // arrive simultaneously (e.g., from frontend auto-resume). + sessionLocks sync.Map // map[string]*sync.Mutex } // ExecutorConfig holds configuration for the Executor @@ -183,6 +189,14 @@ func NewExecutor(agentManager AgentManagerClient, repo repository.Repository, lo } } +// getSessionLock returns a per-session mutex, creating one if it doesn't exist. +// This serializes concurrent resume/launch operations on the same session to prevent +// duplicate agent processes after backend restart. +func (e *Executor) getSessionLock(sessionID string) *sync.Mutex { + val, _ := e.sessionLocks.LoadOrStore(sessionID, &sync.Mutex{}) + return val.(*sync.Mutex) +} + func (e *Executor) applyPreferredShellEnv(ctx context.Context, env map[string]string) map[string]string { if e.shellPrefs == nil { return env @@ -532,6 +546,13 @@ func (e *Executor) ResumeSession(ctx context.Context, session *models.TaskSessio return nil, ErrExecutionNotFound } + // Acquire per-session lock to prevent concurrent resume/launch operations. + // This is critical after backend restart when multiple resume requests may arrive + // simultaneously (e.g., frontend auto-resume hook firing on page open). + sessionLock := e.getSessionLock(session.ID) + sessionLock.Lock() + defer sessionLock.Unlock() + taskModel, err := e.repo.GetTask(ctx, session.TaskID) if err != nil { e.logger.Error("failed to load task for session resume", @@ -562,7 +583,7 @@ func (e *Executor) ResumeSession(ctx context.Context, session *models.TaskSessio SessionID: session.ID, TaskTitle: task.Title, AgentProfileID: session.AgentProfileID, - TaskDescription: "", + TaskDescription: task.Description, Priority: task.Priority, } diff --git a/apps/backend/internal/orchestrator/task_operations.go b/apps/backend/internal/orchestrator/task_operations.go index cae66e384b..1b99e8223d 100644 --- a/apps/backend/internal/orchestrator/task_operations.go +++ b/apps/backend/internal/orchestrator/task_operations.go @@ -606,6 +606,8 @@ func (s *Service) PromptTask(ctx context.Context, taskID, sessionID string, prom // Model is the same, fall through to regular prompt } + previousSessionState := session.State + s.setSessionRunning(ctx, taskID, sessionID) s.startTurnForSession(ctx, sessionID) @@ -619,6 +621,11 @@ func (s *Service) PromptTask(ctx context.Context, taskID, sessionID string, prom zap.String("task_id", taskID), zap.String("session_id", sessionID), zap.Error(err)) + // Revert session state so it doesn't stay stuck in RUNNING. + // Use repo directly to bypass state machine guards that block transitions from terminal states. + _ = s.repo.UpdateTaskSessionState(ctx, sessionID, previousSessionState, "") + _ = s.taskRepo.UpdateTaskState(ctx, taskID, v1.TaskStateReview) + s.completeTurnForSession(ctx, sessionID) return nil, err } return &PromptResult{ diff --git a/apps/backend/internal/task/handlers/message_handlers.go b/apps/backend/internal/task/handlers/message_handlers.go index dde06bd93c..9e0636b14b 100644 --- a/apps/backend/internal/task/handlers/message_handlers.go +++ b/apps/backend/internal/task/handlers/message_handlers.go @@ -154,6 +154,11 @@ func (h *MessageHandlers) wsAddMessage(ctx context.Context, msg *ws.Message) (*w zap.String("session_state", string(sessionResp.Session.State))) return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "Agent is currently processing. Please wait for the current operation to complete.", nil) } + if sessionResp.Session.State == models.TaskSessionStateFailed || + sessionResp.Session.State == models.TaskSessionStateCancelled { + return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, + "Session has ended. Please create a new session to continue.", nil) + } // Get the current task state to determine if we need to transition task, err := h.taskController.GetTask(ctx, dto.GetTaskRequest{ID: req.TaskID}) diff --git a/apps/backend/pkg/claudecode/types.go b/apps/backend/pkg/claudecode/types.go index 26dd7aca45..1c1341b35b 100644 --- a/apps/backend/pkg/claudecode/types.go +++ b/apps/backend/pkg/claudecode/types.go @@ -74,6 +74,7 @@ type CLIMessage struct { DurationMS int64 `json:"duration_ms,omitempty"` DurationAPIMS int64 `json:"duration_api_ms,omitempty"` IsError bool `json:"is_error,omitempty"` + Errors []string `json:"errors,omitempty"` NumTurns int `json:"num_turns,omitempty"` TotalInputTokens int64 `json:"total_input_tokens,omitempty"` TotalOutputTokens int64 `json:"total_output_tokens,omitempty"` diff --git a/apps/packages/theme/src/fonts.css b/apps/packages/theme/src/fonts.css index 58df8f3b74..d2bd494ff2 100644 --- a/apps/packages/theme/src/fonts.css +++ b/apps/packages/theme/src/fonts.css @@ -69,3 +69,11 @@ src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } + +@font-face { + font-family: 'Seti'; + src: url('/fonts/seti/seti.woff') format('woff'); + font-weight: normal; + font-style: normal; + font-display: swap; +} diff --git a/apps/web/app/fonts.css b/apps/web/app/fonts.css index 58df8f3b74..d2bd494ff2 100644 --- a/apps/web/app/fonts.css +++ b/apps/web/app/fonts.css @@ -69,3 +69,11 @@ src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } + +@font-face { + font-family: 'Seti'; + src: url('/fonts/seti/seti.woff') format('woff'); + font-weight: normal; + font-style: normal; + font-display: swap; +} diff --git a/apps/web/components/task-create-dialog.tsx b/apps/web/components/task-create-dialog.tsx index 7ae4a31834..b45ff4a910 100644 --- a/apps/web/components/task-create-dialog.tsx +++ b/apps/web/components/task-create-dialog.tsx @@ -299,7 +299,7 @@ const TaskFormInputs = memo(function TaskFormInputs({ onChange={handleDescriptionChange} onKeyDown={onKeyDown} rows={2} - className={isSessionMode ? 'min-h-[120px] resize-none mt-1.5' : 'min-h-[96px] max-h-[240px] resize-y overflow-auto'} + className={isSessionMode ? 'min-h-[120px] max-h-[240px] resize-none overflow-auto mt-1.5' : 'min-h-[96px] max-h-[240px] resize-y overflow-auto'} required={isSessionMode} /> diff --git a/apps/web/components/task/chat/chat-input-container.tsx b/apps/web/components/task/chat/chat-input-container.tsx index b287a0a621..b445d2096a 100644 --- a/apps/web/components/task/chat/chat-input-container.tsx +++ b/apps/web/components/task/chat/chat-input-container.tsx @@ -3,7 +3,9 @@ import { useRef, useCallback, useState, useEffect, memo, forwardRef, useImperativeHandle, type KeyboardEvent as ReactKeyboardEvent } from 'react'; import { IconArrowUp, + IconAlertTriangle, IconListCheck, + IconPlus, IconPlayerStopFilled, } from '@tabler/icons-react'; import { GridSpinner } from '@/components/grid-spinner'; @@ -14,6 +16,7 @@ import { SHORTCUTS } from '@/lib/keyboard/constants'; import { KeyboardShortcutTooltip } from '@/components/keyboard-shortcut-tooltip'; import { TokenUsageDisplay } from '@/components/task/chat/token-usage-display'; import { SessionsDropdown } from '@/components/task/sessions-dropdown'; +import { TaskCreateDialog } from '@/components/task-create-dialog'; import { ModelSelector } from '@/components/task/model-selector'; import { RichTextInput, type RichTextInputHandle } from './rich-text-input'; import { MentionMenu } from './mention-menu'; @@ -190,6 +193,8 @@ type ChatInputContainerProps = { submitKey?: 'enter' | 'cmd_enter'; /** Whether the current agent has commands (affects placeholder) */ hasAgentCommands?: boolean; + /** Whether the session is in a terminal state (FAILED/CANCELLED) */ + isFailed?: boolean; }; export const ChatInputContainer = forwardRef( @@ -217,6 +222,7 @@ export const ChatInputContainer = forwardRef([]); + const [showNewSessionDialog, setShowNewSessionDialog] = useState(false); const inputRef = useRef(null); // Expose imperative handle for parent @@ -420,8 +427,8 @@ export const ChatInputContainer = forwardRef 0; @@ -435,6 +442,44 @@ export const ChatInputContainer = forwardRef +
+
+
+ + This session has ended. Start a new session to continue. +
+ +
+
+ + + ); + } + return (
) ) : ( - + )} {node.name} {!node.is_dir && onDeleteFile && ( @@ -504,7 +514,12 @@ export function FileBrowser({ sessionId, onOpenFile, onDeleteFile, isSearchOpen className="group flex items-center gap-1 py-1 px-2 hover:bg-muted/50 cursor-pointer rounded text-sm" onClick={() => openFileByPath(path)} > - + {folder && {folder}/} {name} diff --git a/apps/web/components/task/task-chat-panel.tsx b/apps/web/components/task/task-chat-panel.tsx index 25231744f4..3d85be70d7 100644 --- a/apps/web/components/task/task-chat-panel.tsx +++ b/apps/web/components/task/task-chat-panel.tsx @@ -57,6 +57,7 @@ export const TaskChatPanel = memo(function TaskChatPanel({ isStarting, isWorking, isAgentBusy, + isFailed, } = useSessionState(sessionId); // Plan mode state from store (persisted per session) @@ -281,6 +282,7 @@ export const TaskChatPanel = memo(function TaskChatPanel({ onCommentClick={onOpenFileAtLine ? (comment) => onOpenFileAtLine(comment.filePath) : undefined} submitKey={chatSubmitKey} hasAgentCommands={hasAgentCommands} + isFailed={isFailed} />
diff --git a/apps/web/components/ui/file-icon.tsx b/apps/web/components/ui/file-icon.tsx new file mode 100644 index 0000000000..2d643f2e39 --- /dev/null +++ b/apps/web/components/ui/file-icon.tsx @@ -0,0 +1,163 @@ +import React, { useMemo } from "react"; +import setiIconTheme from "@/lib/assets/file-icons/seti-icon-theme.json"; + +interface SetiIconDefinition { + fontCharacter?: string; + fontColor?: string; +} + +interface FileIconGlyph { + character: string; + color?: string; +} + +const setiIconDefinitions: Record = setiIconTheme.iconDefinitions; +const setiDefaultIconId = setiIconTheme.file; +const setiFileNames = setiIconTheme.fileNames as Record; +const setiFileExtensions = setiIconTheme.fileExtensions as Record; +const setiLanguageIds = setiIconTheme.languageIds as Record; + +const setiDefaultIconDefinition: SetiIconDefinition = setiIconDefinitions[setiDefaultIconId] ?? { + fontCharacter: "\\E023", +}; + +const decodeFontCharacter = (encoded?: string): string => { + if (!encoded) return ""; + if (!encoded.startsWith("\\")) return encoded; + + const hex = encoded.slice(1); + const codePoint = Number.parseInt(hex, 16); + if (Number.isNaN(codePoint)) return ""; + + return String.fromCodePoint(codePoint); +}; + +const collectExtensionCandidates = (fileName: string): string[] => { + const parts = fileName.split("."); + if (parts.length <= 1) return []; + + const candidates: string[] = []; + for (let i = 1; i < parts.length; i++) { + const candidate = parts.slice(i).join("."); + if (candidate) { + candidates.push(candidate); + } + } + + return candidates; +}; + +const resolveSetiIconId = (fileName: string): string | undefined => { + const direct = setiFileNames[fileName]; + if (direct) return direct; + + const lowerName = fileName.toLowerCase(); + if (lowerName !== fileName) { + const lowerDirect = setiFileNames[lowerName]; + if (lowerDirect) return lowerDirect; + } + + if (fileName.startsWith(".") && fileName.length > 1) { + const withoutDot = fileName.slice(1); + const withoutDotMatch = setiFileNames[withoutDot]; + if (withoutDotMatch) return withoutDotMatch; + } + + const extensionCandidates = collectExtensionCandidates(fileName); + + for (const candidate of extensionCandidates) { + const extMatch = setiFileExtensions[candidate]; + if (extMatch) return extMatch; + + const lowerCandidate = candidate.toLowerCase(); + if (lowerCandidate !== candidate) { + const lowerExtMatch = setiFileExtensions[lowerCandidate]; + if (lowerExtMatch) return lowerExtMatch; + } + } + + const languageMatch = setiLanguageIds[lowerName]; + if (languageMatch) return languageMatch; + + for (const candidate of extensionCandidates) { + const languageIdMatch = setiLanguageIds[candidate]; + if (languageIdMatch) return languageIdMatch; + + const lowerCandidate = candidate.toLowerCase(); + if (lowerCandidate !== candidate) { + const lowerLanguageIdMatch = setiLanguageIds[lowerCandidate]; + if (lowerLanguageIdMatch) return lowerLanguageIdMatch; + } + } + + if (fileName.startsWith(".") && fileName.length > 1) { + const trimmedLower = fileName.slice(1).toLowerCase(); + const trimmedLanguageMatch = setiLanguageIds[trimmedLower]; + if (trimmedLanguageMatch) return trimmedLanguageMatch; + } + + return undefined; +}; + +const getSetiIconForFile = (fileName: string): FileIconGlyph => { + if (!fileName) { + return { + character: decodeFontCharacter(setiDefaultIconDefinition.fontCharacter) || " ", + color: setiDefaultIconDefinition.fontColor, + }; + } + + const iconId = resolveSetiIconId(fileName); + const iconDefinition = iconId ? setiIconDefinitions[iconId] : undefined; + + return { + character: + decodeFontCharacter(iconDefinition?.fontCharacter) || + decodeFontCharacter(setiDefaultIconDefinition.fontCharacter) || + " ", + color: iconDefinition?.fontColor ?? setiDefaultIconDefinition.fontColor, + }; +}; + +const BASE_ICON_STYLE: React.CSSProperties = { + fontFamily: + '"Seti", "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', + fontSize: 14, + lineHeight: 1, + display: "inline-flex", + alignItems: "center", + justifyContent: "center", + minWidth: "1rem", + height: "1rem", + userSelect: "none", + fontStyle: "normal", + fontWeight: "normal", + letterSpacing: "normal", +}; + +export interface FileIconProps { + fileName?: string | null; + filePath?: string | null; + className?: string; + style?: React.CSSProperties; +} + +export const FileIcon: React.FC = ({ fileName, filePath, className, style }) => { + const targetName = fileName ?? (filePath ? (filePath.split("/").pop() ?? "") : ""); + + const icon = useMemo(() => getSetiIconForFile(targetName ?? ""), [targetName]); + + if (!icon.character.trim()) { + return null; + } + + return ( + + ); +}; diff --git a/apps/web/hooks/domains/session/use-session-state.ts b/apps/web/hooks/domains/session/use-session-state.ts index 6a22106ad7..616c59d346 100644 --- a/apps/web/hooks/domains/session/use-session-state.ts +++ b/apps/web/hooks/domains/session/use-session-state.ts @@ -14,6 +14,7 @@ export function useSessionState(sessionId: string | null) { const isStarting = session?.state === 'STARTING'; const isWorking = isStarting || session?.state === 'RUNNING'; const isAgentBusy = session?.state === 'CREATED' || session?.state === 'RUNNING'; + const isFailed = session?.state === 'FAILED' || session?.state === 'CANCELLED'; return { resolvedSessionId, @@ -24,5 +25,6 @@ export function useSessionState(sessionId: string | null) { isStarting, isWorking, isAgentBusy, + isFailed, }; } diff --git a/apps/web/lib/assets/file-icons/seti-icon-theme.json b/apps/web/lib/assets/file-icons/seti-icon-theme.json new file mode 100644 index 0000000000..2610c1a3a6 --- /dev/null +++ b/apps/web/lib/assets/file-icons/seti-icon-theme.json @@ -0,0 +1,2408 @@ +{ + "information_for_contributors": [ + "This file has been generated from data in https://github.com/jesseweed/seti-ui", + "- icon definitions: https://github.com/jesseweed/seti-ui/blob/master/styles/_fonts/seti.less", + "- icon colors: https://github.com/jesseweed/seti-ui/blob/master/styles/ui-variables.less", + "- file associations: https://github.com/jesseweed/seti-ui/blob/master/styles/components/icons/mapping.less", + "If you want to provide a fix or improvement, please create a pull request against the jesseweed/seti-ui repository.", + "Once accepted there, we are happy to receive an update request." + ], + "fonts": [ + { + "id": "seti", + "src": [ + { + "path": "./seti.woff", + "format": "woff" + } + ], + "weight": "normal", + "style": "normal", + "size": "150%" + } + ], + "iconDefinitions": { + "_R_light": { + "fontCharacter": "\\E001", + "fontColor": "#498ba7" + }, + "_R": { + "fontCharacter": "\\E001", + "fontColor": "#519aba" + }, + "_argdown_light": { + "fontCharacter": "\\E003", + "fontColor": "#498ba7" + }, + "_argdown": { + "fontCharacter": "\\E003", + "fontColor": "#519aba" + }, + "_asm_light": { + "fontCharacter": "\\E004", + "fontColor": "#b8383d" + }, + "_asm": { + "fontCharacter": "\\E004", + "fontColor": "#cc3e44" + }, + "_audio_light": { + "fontCharacter": "\\E005", + "fontColor": "#9068b0" + }, + "_audio": { + "fontCharacter": "\\E005", + "fontColor": "#a074c4" + }, + "_babel_light": { + "fontCharacter": "\\E006", + "fontColor": "#b7b73b" + }, + "_babel": { + "fontCharacter": "\\E006", + "fontColor": "#cbcb41" + }, + "_bazel_light": { + "fontCharacter": "\\E007", + "fontColor": "#7fae42" + }, + "_bazel": { + "fontCharacter": "\\E007", + "fontColor": "#8dc149" + }, + "_bazel_1_light": { + "fontCharacter": "\\E007", + "fontColor": "#455155" + }, + "_bazel_1": { + "fontCharacter": "\\E007", + "fontColor": "#4d5a5e" + }, + "_bicep_light": { + "fontCharacter": "\\E008", + "fontColor": "#498ba7" + }, + "_bicep": { + "fontCharacter": "\\E008", + "fontColor": "#519aba" + }, + "_bower_light": { + "fontCharacter": "\\E009", + "fontColor": "#cc6d2e" + }, + "_bower": { + "fontCharacter": "\\E009", + "fontColor": "#e37933" + }, + "_bsl_light": { + "fontCharacter": "\\E00A", + "fontColor": "#b8383d" + }, + "_bsl": { + "fontCharacter": "\\E00A", + "fontColor": "#cc3e44" + }, + "_c_light": { + "fontCharacter": "\\E00C", + "fontColor": "#498ba7" + }, + "_c": { + "fontCharacter": "\\E00C", + "fontColor": "#519aba" + }, + "_c-sharp_light": { + "fontCharacter": "\\E00B", + "fontColor": "#498ba7" + }, + "_c-sharp": { + "fontCharacter": "\\E00B", + "fontColor": "#519aba" + }, + "_c_1_light": { + "fontCharacter": "\\E00C", + "fontColor": "#9068b0" + }, + "_c_1": { + "fontCharacter": "\\E00C", + "fontColor": "#a074c4" + }, + "_c_2_light": { + "fontCharacter": "\\E00C", + "fontColor": "#b7b73b" + }, + "_c_2": { + "fontCharacter": "\\E00C", + "fontColor": "#cbcb41" + }, + "_cake_light": { + "fontCharacter": "\\E00D", + "fontColor": "#b8383d" + }, + "_cake": { + "fontCharacter": "\\E00D", + "fontColor": "#cc3e44" + }, + "_cake_php_light": { + "fontCharacter": "\\E00E", + "fontColor": "#b8383d" + }, + "_cake_php": { + "fontCharacter": "\\E00E", + "fontColor": "#cc3e44" + }, + "_clock_light": { + "fontCharacter": "\\E012", + "fontColor": "#498ba7" + }, + "_clock": { + "fontCharacter": "\\E012", + "fontColor": "#519aba" + }, + "_clock_1_light": { + "fontCharacter": "\\E012", + "fontColor": "#627379" + }, + "_clock_1": { + "fontCharacter": "\\E012", + "fontColor": "#6d8086" + }, + "_clojure_light": { + "fontCharacter": "\\E013", + "fontColor": "#7fae42" + }, + "_clojure": { + "fontCharacter": "\\E013", + "fontColor": "#8dc149" + }, + "_clojure_1_light": { + "fontCharacter": "\\E013", + "fontColor": "#498ba7" + }, + "_clojure_1": { + "fontCharacter": "\\E013", + "fontColor": "#519aba" + }, + "_code-climate_light": { + "fontCharacter": "\\E014", + "fontColor": "#7fae42" + }, + "_code-climate": { + "fontCharacter": "\\E014", + "fontColor": "#8dc149" + }, + "_code-search_light": { + "fontCharacter": "\\E015", + "fontColor": "#9068b0" + }, + "_code-search": { + "fontCharacter": "\\E015", + "fontColor": "#a074c4" + }, + "_coffee_light": { + "fontCharacter": "\\E016", + "fontColor": "#b7b73b" + }, + "_coffee": { + "fontCharacter": "\\E016", + "fontColor": "#cbcb41" + }, + "_coldfusion_light": { + "fontCharacter": "\\E018", + "fontColor": "#498ba7" + }, + "_coldfusion": { + "fontCharacter": "\\E018", + "fontColor": "#519aba" + }, + "_config_light": { + "fontCharacter": "\\E019", + "fontColor": "#627379" + }, + "_config": { + "fontCharacter": "\\E019", + "fontColor": "#6d8086" + }, + "_cpp_light": { + "fontCharacter": "\\E01A", + "fontColor": "#498ba7" + }, + "_cpp": { + "fontCharacter": "\\E01A", + "fontColor": "#519aba" + }, + "_cpp_1_light": { + "fontCharacter": "\\E01A", + "fontColor": "#9068b0" + }, + "_cpp_1": { + "fontCharacter": "\\E01A", + "fontColor": "#a074c4" + }, + "_cpp_2_light": { + "fontCharacter": "\\E01A", + "fontColor": "#b7b73b" + }, + "_cpp_2": { + "fontCharacter": "\\E01A", + "fontColor": "#cbcb41" + }, + "_crystal_light": { + "fontCharacter": "\\E01B", + "fontColor": "#bfc2c1" + }, + "_crystal": { + "fontCharacter": "\\E01B", + "fontColor": "#d4d7d6" + }, + "_crystal_embedded_light": { + "fontCharacter": "\\E01C", + "fontColor": "#bfc2c1" + }, + "_crystal_embedded": { + "fontCharacter": "\\E01C", + "fontColor": "#d4d7d6" + }, + "_css_light": { + "fontCharacter": "\\E01D", + "fontColor": "#498ba7" + }, + "_css": { + "fontCharacter": "\\E01D", + "fontColor": "#519aba" + }, + "_csv_light": { + "fontCharacter": "\\E01E", + "fontColor": "#7fae42" + }, + "_csv": { + "fontCharacter": "\\E01E", + "fontColor": "#8dc149" + }, + "_cu_light": { + "fontCharacter": "\\E01F", + "fontColor": "#7fae42" + }, + "_cu": { + "fontCharacter": "\\E01F", + "fontColor": "#8dc149" + }, + "_cu_1_light": { + "fontCharacter": "\\E01F", + "fontColor": "#9068b0" + }, + "_cu_1": { + "fontCharacter": "\\E01F", + "fontColor": "#a074c4" + }, + "_d_light": { + "fontCharacter": "\\E020", + "fontColor": "#b8383d" + }, + "_d": { + "fontCharacter": "\\E020", + "fontColor": "#cc3e44" + }, + "_dart_light": { + "fontCharacter": "\\E021", + "fontColor": "#498ba7" + }, + "_dart": { + "fontCharacter": "\\E021", + "fontColor": "#519aba" + }, + "_db_light": { + "fontCharacter": "\\E022", + "fontColor": "#dd4b78" + }, + "_db": { + "fontCharacter": "\\E022", + "fontColor": "#f55385" + }, + "_db_1_light": { + "fontCharacter": "\\E022", + "fontColor": "#498ba7" + }, + "_db_1": { + "fontCharacter": "\\E022", + "fontColor": "#519aba" + }, + "_default_light": { + "fontCharacter": "\\E023", + "fontColor": "#bfc2c1" + }, + "_default": { + "fontCharacter": "\\E023", + "fontColor": "#d4d7d6" + }, + "_docker_light": { + "fontCharacter": "\\E025", + "fontColor": "#498ba7" + }, + "_docker": { + "fontCharacter": "\\E025", + "fontColor": "#519aba" + }, + "_docker_1_light": { + "fontCharacter": "\\E025", + "fontColor": "#455155" + }, + "_docker_1": { + "fontCharacter": "\\E025", + "fontColor": "#4d5a5e" + }, + "_docker_2_light": { + "fontCharacter": "\\E025", + "fontColor": "#7fae42" + }, + "_docker_2": { + "fontCharacter": "\\E025", + "fontColor": "#8dc149" + }, + "_docker_3_light": { + "fontCharacter": "\\E025", + "fontColor": "#dd4b78" + }, + "_docker_3": { + "fontCharacter": "\\E025", + "fontColor": "#f55385" + }, + "_ejs_light": { + "fontCharacter": "\\E027", + "fontColor": "#b7b73b" + }, + "_ejs": { + "fontCharacter": "\\E027", + "fontColor": "#cbcb41" + }, + "_elixir_light": { + "fontCharacter": "\\E028", + "fontColor": "#9068b0" + }, + "_elixir": { + "fontCharacter": "\\E028", + "fontColor": "#a074c4" + }, + "_elixir_script_light": { + "fontCharacter": "\\E029", + "fontColor": "#9068b0" + }, + "_elixir_script": { + "fontCharacter": "\\E029", + "fontColor": "#a074c4" + }, + "_elm_light": { + "fontCharacter": "\\E02A", + "fontColor": "#498ba7" + }, + "_elm": { + "fontCharacter": "\\E02A", + "fontColor": "#519aba" + }, + "_eslint_light": { + "fontCharacter": "\\E02C", + "fontColor": "#9068b0" + }, + "_eslint": { + "fontCharacter": "\\E02C", + "fontColor": "#a074c4" + }, + "_eslint_1_light": { + "fontCharacter": "\\E02C", + "fontColor": "#455155" + }, + "_eslint_1": { + "fontCharacter": "\\E02C", + "fontColor": "#4d5a5e" + }, + "_ethereum_light": { + "fontCharacter": "\\E02D", + "fontColor": "#498ba7" + }, + "_ethereum": { + "fontCharacter": "\\E02D", + "fontColor": "#519aba" + }, + "_f-sharp_light": { + "fontCharacter": "\\E02E", + "fontColor": "#498ba7" + }, + "_f-sharp": { + "fontCharacter": "\\E02E", + "fontColor": "#519aba" + }, + "_favicon_light": { + "fontCharacter": "\\E02F", + "fontColor": "#b7b73b" + }, + "_favicon": { + "fontCharacter": "\\E02F", + "fontColor": "#cbcb41" + }, + "_firebase_light": { + "fontCharacter": "\\E030", + "fontColor": "#cc6d2e" + }, + "_firebase": { + "fontCharacter": "\\E030", + "fontColor": "#e37933" + }, + "_firefox_light": { + "fontCharacter": "\\E031", + "fontColor": "#cc6d2e" + }, + "_firefox": { + "fontCharacter": "\\E031", + "fontColor": "#e37933" + }, + "_font_light": { + "fontCharacter": "\\E033", + "fontColor": "#b8383d" + }, + "_font": { + "fontCharacter": "\\E033", + "fontColor": "#cc3e44" + }, + "_git_light": { + "fontCharacter": "\\E034", + "fontColor": "#3b4b52" + }, + "_git": { + "fontCharacter": "\\E034", + "fontColor": "#41535b" + }, + "_github_light": { + "fontCharacter": "\\E037", + "fontColor": "#bfc2c1" + }, + "_github": { + "fontCharacter": "\\E037", + "fontColor": "#d4d7d6" + }, + "_gitlab_light": { + "fontCharacter": "\\E038", + "fontColor": "#cc6d2e" + }, + "_gitlab": { + "fontCharacter": "\\E038", + "fontColor": "#e37933" + }, + "_go_light": { + "fontCharacter": "\\E039", + "fontColor": "#498ba7" + }, + "_go": { + "fontCharacter": "\\E039", + "fontColor": "#519aba" + }, + "_go2_light": { + "fontCharacter": "\\E03A", + "fontColor": "#498ba7" + }, + "_go2": { + "fontCharacter": "\\E03A", + "fontColor": "#519aba" + }, + "_godot_light": { + "fontCharacter": "\\E03B", + "fontColor": "#498ba7" + }, + "_godot": { + "fontCharacter": "\\E03B", + "fontColor": "#519aba" + }, + "_godot_1_light": { + "fontCharacter": "\\E03B", + "fontColor": "#b8383d" + }, + "_godot_1": { + "fontCharacter": "\\E03B", + "fontColor": "#cc3e44" + }, + "_godot_2_light": { + "fontCharacter": "\\E03B", + "fontColor": "#b7b73b" + }, + "_godot_2": { + "fontCharacter": "\\E03B", + "fontColor": "#cbcb41" + }, + "_godot_3_light": { + "fontCharacter": "\\E03B", + "fontColor": "#9068b0" + }, + "_godot_3": { + "fontCharacter": "\\E03B", + "fontColor": "#a074c4" + }, + "_gradle_light": { + "fontCharacter": "\\E03C", + "fontColor": "#498ba7" + }, + "_gradle": { + "fontCharacter": "\\E03C", + "fontColor": "#519aba" + }, + "_grails_light": { + "fontCharacter": "\\E03D", + "fontColor": "#7fae42" + }, + "_grails": { + "fontCharacter": "\\E03D", + "fontColor": "#8dc149" + }, + "_graphql_light": { + "fontCharacter": "\\E03E", + "fontColor": "#dd4b78" + }, + "_graphql": { + "fontCharacter": "\\E03E", + "fontColor": "#f55385" + }, + "_grunt_light": { + "fontCharacter": "\\E03F", + "fontColor": "#cc6d2e" + }, + "_grunt": { + "fontCharacter": "\\E03F", + "fontColor": "#e37933" + }, + "_gulp_light": { + "fontCharacter": "\\E040", + "fontColor": "#b8383d" + }, + "_gulp": { + "fontCharacter": "\\E040", + "fontColor": "#cc3e44" + }, + "_hacklang_light": { + "fontCharacter": "\\E041", + "fontColor": "#cc6d2e" + }, + "_hacklang": { + "fontCharacter": "\\E041", + "fontColor": "#e37933" + }, + "_haml_light": { + "fontCharacter": "\\E042", + "fontColor": "#b8383d" + }, + "_haml": { + "fontCharacter": "\\E042", + "fontColor": "#cc3e44" + }, + "_happenings_light": { + "fontCharacter": "\\E043", + "fontColor": "#498ba7" + }, + "_happenings": { + "fontCharacter": "\\E043", + "fontColor": "#519aba" + }, + "_haskell_light": { + "fontCharacter": "\\E044", + "fontColor": "#9068b0" + }, + "_haskell": { + "fontCharacter": "\\E044", + "fontColor": "#a074c4" + }, + "_haxe_light": { + "fontCharacter": "\\E045", + "fontColor": "#cc6d2e" + }, + "_haxe": { + "fontCharacter": "\\E045", + "fontColor": "#e37933" + }, + "_haxe_1_light": { + "fontCharacter": "\\E045", + "fontColor": "#b7b73b" + }, + "_haxe_1": { + "fontCharacter": "\\E045", + "fontColor": "#cbcb41" + }, + "_haxe_2_light": { + "fontCharacter": "\\E045", + "fontColor": "#498ba7" + }, + "_haxe_2": { + "fontCharacter": "\\E045", + "fontColor": "#519aba" + }, + "_haxe_3_light": { + "fontCharacter": "\\E045", + "fontColor": "#9068b0" + }, + "_haxe_3": { + "fontCharacter": "\\E045", + "fontColor": "#a074c4" + }, + "_heroku_light": { + "fontCharacter": "\\E046", + "fontColor": "#9068b0" + }, + "_heroku": { + "fontCharacter": "\\E046", + "fontColor": "#a074c4" + }, + "_hex_light": { + "fontCharacter": "\\E047", + "fontColor": "#b8383d" + }, + "_hex": { + "fontCharacter": "\\E047", + "fontColor": "#cc3e44" + }, + "_html_light": { + "fontCharacter": "\\E048", + "fontColor": "#498ba7" + }, + "_html": { + "fontCharacter": "\\E048", + "fontColor": "#519aba" + }, + "_html_1_light": { + "fontCharacter": "\\E048", + "fontColor": "#7fae42" + }, + "_html_1": { + "fontCharacter": "\\E048", + "fontColor": "#8dc149" + }, + "_html_2_light": { + "fontCharacter": "\\E048", + "fontColor": "#b7b73b" + }, + "_html_2": { + "fontCharacter": "\\E048", + "fontColor": "#cbcb41" + }, + "_html_3_light": { + "fontCharacter": "\\E048", + "fontColor": "#cc6d2e" + }, + "_html_3": { + "fontCharacter": "\\E048", + "fontColor": "#e37933" + }, + "_html_erb_light": { + "fontCharacter": "\\E049", + "fontColor": "#b8383d" + }, + "_html_erb": { + "fontCharacter": "\\E049", + "fontColor": "#cc3e44" + }, + "_ignored_light": { + "fontCharacter": "\\E04A", + "fontColor": "#3b4b52" + }, + "_ignored": { + "fontCharacter": "\\E04A", + "fontColor": "#41535b" + }, + "_illustrator_light": { + "fontCharacter": "\\E04B", + "fontColor": "#b7b73b" + }, + "_illustrator": { + "fontCharacter": "\\E04B", + "fontColor": "#cbcb41" + }, + "_image_light": { + "fontCharacter": "\\E04C", + "fontColor": "#9068b0" + }, + "_image": { + "fontCharacter": "\\E04C", + "fontColor": "#a074c4" + }, + "_info_light": { + "fontCharacter": "\\E04D", + "fontColor": "#498ba7" + }, + "_info": { + "fontCharacter": "\\E04D", + "fontColor": "#519aba" + }, + "_ionic_light": { + "fontCharacter": "\\E04E", + "fontColor": "#498ba7" + }, + "_ionic": { + "fontCharacter": "\\E04E", + "fontColor": "#519aba" + }, + "_jade_light": { + "fontCharacter": "\\E04F", + "fontColor": "#b8383d" + }, + "_jade": { + "fontCharacter": "\\E04F", + "fontColor": "#cc3e44" + }, + "_java_light": { + "fontCharacter": "\\E050", + "fontColor": "#b8383d" + }, + "_java": { + "fontCharacter": "\\E050", + "fontColor": "#cc3e44" + }, + "_java_1_light": { + "fontCharacter": "\\E050", + "fontColor": "#498ba7" + }, + "_java_1": { + "fontCharacter": "\\E050", + "fontColor": "#519aba" + }, + "_javascript_light": { + "fontCharacter": "\\E051", + "fontColor": "#b7b73b" + }, + "_javascript": { + "fontCharacter": "\\E051", + "fontColor": "#cbcb41" + }, + "_javascript_1_light": { + "fontCharacter": "\\E051", + "fontColor": "#cc6d2e" + }, + "_javascript_1": { + "fontCharacter": "\\E051", + "fontColor": "#e37933" + }, + "_javascript_2_light": { + "fontCharacter": "\\E051", + "fontColor": "#498ba7" + }, + "_javascript_2": { + "fontCharacter": "\\E051", + "fontColor": "#519aba" + }, + "_jenkins_light": { + "fontCharacter": "\\E052", + "fontColor": "#b8383d" + }, + "_jenkins": { + "fontCharacter": "\\E052", + "fontColor": "#cc3e44" + }, + "_jinja_light": { + "fontCharacter": "\\E053", + "fontColor": "#b8383d" + }, + "_jinja": { + "fontCharacter": "\\E053", + "fontColor": "#cc3e44" + }, + "_json_light": { + "fontCharacter": "\\E055", + "fontColor": "#b7b73b" + }, + "_json": { + "fontCharacter": "\\E055", + "fontColor": "#cbcb41" + }, + "_json_1_light": { + "fontCharacter": "\\E055", + "fontColor": "#7fae42" + }, + "_json_1": { + "fontCharacter": "\\E055", + "fontColor": "#8dc149" + }, + "_julia_light": { + "fontCharacter": "\\E056", + "fontColor": "#9068b0" + }, + "_julia": { + "fontCharacter": "\\E056", + "fontColor": "#a074c4" + }, + "_karma_light": { + "fontCharacter": "\\E057", + "fontColor": "#7fae42" + }, + "_karma": { + "fontCharacter": "\\E057", + "fontColor": "#8dc149" + }, + "_kotlin_light": { + "fontCharacter": "\\E058", + "fontColor": "#cc6d2e" + }, + "_kotlin": { + "fontCharacter": "\\E058", + "fontColor": "#e37933" + }, + "_less_light": { + "fontCharacter": "\\E059", + "fontColor": "#498ba7" + }, + "_less": { + "fontCharacter": "\\E059", + "fontColor": "#519aba" + }, + "_license_light": { + "fontCharacter": "\\E05A", + "fontColor": "#b7b73b" + }, + "_license": { + "fontCharacter": "\\E05A", + "fontColor": "#cbcb41" + }, + "_license_1_light": { + "fontCharacter": "\\E05A", + "fontColor": "#cc6d2e" + }, + "_license_1": { + "fontCharacter": "\\E05A", + "fontColor": "#e37933" + }, + "_license_2_light": { + "fontCharacter": "\\E05A", + "fontColor": "#b8383d" + }, + "_license_2": { + "fontCharacter": "\\E05A", + "fontColor": "#cc3e44" + }, + "_liquid_light": { + "fontCharacter": "\\E05B", + "fontColor": "#7fae42" + }, + "_liquid": { + "fontCharacter": "\\E05B", + "fontColor": "#8dc149" + }, + "_livescript_light": { + "fontCharacter": "\\E05C", + "fontColor": "#498ba7" + }, + "_livescript": { + "fontCharacter": "\\E05C", + "fontColor": "#519aba" + }, + "_lock_light": { + "fontCharacter": "\\E05D", + "fontColor": "#7fae42" + }, + "_lock": { + "fontCharacter": "\\E05D", + "fontColor": "#8dc149" + }, + "_lua_light": { + "fontCharacter": "\\E05E", + "fontColor": "#498ba7" + }, + "_lua": { + "fontCharacter": "\\E05E", + "fontColor": "#519aba" + }, + "_makefile_light": { + "fontCharacter": "\\E05F", + "fontColor": "#cc6d2e" + }, + "_makefile": { + "fontCharacter": "\\E05F", + "fontColor": "#e37933" + }, + "_makefile_1_light": { + "fontCharacter": "\\E05F", + "fontColor": "#9068b0" + }, + "_makefile_1": { + "fontCharacter": "\\E05F", + "fontColor": "#a074c4" + }, + "_makefile_2_light": { + "fontCharacter": "\\E05F", + "fontColor": "#627379" + }, + "_makefile_2": { + "fontCharacter": "\\E05F", + "fontColor": "#6d8086" + }, + "_makefile_3_light": { + "fontCharacter": "\\E05F", + "fontColor": "#498ba7" + }, + "_makefile_3": { + "fontCharacter": "\\E05F", + "fontColor": "#519aba" + }, + "_markdown_light": { + "fontCharacter": "\\E060", + "fontColor": "#498ba7" + }, + "_markdown": { + "fontCharacter": "\\E060", + "fontColor": "#519aba" + }, + "_maven_light": { + "fontCharacter": "\\E061", + "fontColor": "#b8383d" + }, + "_maven": { + "fontCharacter": "\\E061", + "fontColor": "#cc3e44" + }, + "_mdo_light": { + "fontCharacter": "\\E062", + "fontColor": "#b8383d" + }, + "_mdo": { + "fontCharacter": "\\E062", + "fontColor": "#cc3e44" + }, + "_mustache_light": { + "fontCharacter": "\\E063", + "fontColor": "#cc6d2e" + }, + "_mustache": { + "fontCharacter": "\\E063", + "fontColor": "#e37933" + }, + "_nim_light": { + "fontCharacter": "\\E065", + "fontColor": "#b7b73b" + }, + "_nim": { + "fontCharacter": "\\E065", + "fontColor": "#cbcb41" + }, + "_notebook_light": { + "fontCharacter": "\\E066", + "fontColor": "#498ba7" + }, + "_notebook": { + "fontCharacter": "\\E066", + "fontColor": "#519aba" + }, + "_npm_light": { + "fontCharacter": "\\E067", + "fontColor": "#3b4b52" + }, + "_npm": { + "fontCharacter": "\\E067", + "fontColor": "#41535b" + }, + "_npm_1_light": { + "fontCharacter": "\\E067", + "fontColor": "#b8383d" + }, + "_npm_1": { + "fontCharacter": "\\E067", + "fontColor": "#cc3e44" + }, + "_npm_ignored_light": { + "fontCharacter": "\\E068", + "fontColor": "#3b4b52" + }, + "_npm_ignored": { + "fontCharacter": "\\E068", + "fontColor": "#41535b" + }, + "_nunjucks_light": { + "fontCharacter": "\\E069", + "fontColor": "#7fae42" + }, + "_nunjucks": { + "fontCharacter": "\\E069", + "fontColor": "#8dc149" + }, + "_ocaml_light": { + "fontCharacter": "\\E06A", + "fontColor": "#cc6d2e" + }, + "_ocaml": { + "fontCharacter": "\\E06A", + "fontColor": "#e37933" + }, + "_odata_light": { + "fontCharacter": "\\E06B", + "fontColor": "#cc6d2e" + }, + "_odata": { + "fontCharacter": "\\E06B", + "fontColor": "#e37933" + }, + "_pddl_light": { + "fontCharacter": "\\E06C", + "fontColor": "#9068b0" + }, + "_pddl": { + "fontCharacter": "\\E06C", + "fontColor": "#a074c4" + }, + "_pdf_light": { + "fontCharacter": "\\E06D", + "fontColor": "#b8383d" + }, + "_pdf": { + "fontCharacter": "\\E06D", + "fontColor": "#cc3e44" + }, + "_perl_light": { + "fontCharacter": "\\E06E", + "fontColor": "#498ba7" + }, + "_perl": { + "fontCharacter": "\\E06E", + "fontColor": "#519aba" + }, + "_photoshop_light": { + "fontCharacter": "\\E06F", + "fontColor": "#498ba7" + }, + "_photoshop": { + "fontCharacter": "\\E06F", + "fontColor": "#519aba" + }, + "_php_light": { + "fontCharacter": "\\E070", + "fontColor": "#9068b0" + }, + "_php": { + "fontCharacter": "\\E070", + "fontColor": "#a074c4" + }, + "_pipeline_light": { + "fontCharacter": "\\E071", + "fontColor": "#cc6d2e" + }, + "_pipeline": { + "fontCharacter": "\\E071", + "fontColor": "#e37933" + }, + "_plan_light": { + "fontCharacter": "\\E072", + "fontColor": "#7fae42" + }, + "_plan": { + "fontCharacter": "\\E072", + "fontColor": "#8dc149" + }, + "_platformio_light": { + "fontCharacter": "\\E073", + "fontColor": "#cc6d2e" + }, + "_platformio": { + "fontCharacter": "\\E073", + "fontColor": "#e37933" + }, + "_powershell_light": { + "fontCharacter": "\\E074", + "fontColor": "#498ba7" + }, + "_powershell": { + "fontCharacter": "\\E074", + "fontColor": "#519aba" + }, + "_prisma_light": { + "fontCharacter": "\\E075", + "fontColor": "#498ba7" + }, + "_prisma": { + "fontCharacter": "\\E075", + "fontColor": "#519aba" + }, + "_prolog_light": { + "fontCharacter": "\\E077", + "fontColor": "#cc6d2e" + }, + "_prolog": { + "fontCharacter": "\\E077", + "fontColor": "#e37933" + }, + "_pug_light": { + "fontCharacter": "\\E078", + "fontColor": "#b8383d" + }, + "_pug": { + "fontCharacter": "\\E078", + "fontColor": "#cc3e44" + }, + "_puppet_light": { + "fontCharacter": "\\E079", + "fontColor": "#b7b73b" + }, + "_puppet": { + "fontCharacter": "\\E079", + "fontColor": "#cbcb41" + }, + "_purescript_light": { + "fontCharacter": "\\E07A", + "fontColor": "#bfc2c1" + }, + "_purescript": { + "fontCharacter": "\\E07A", + "fontColor": "#d4d7d6" + }, + "_python_light": { + "fontCharacter": "\\E07B", + "fontColor": "#498ba7" + }, + "_python": { + "fontCharacter": "\\E07B", + "fontColor": "#519aba" + }, + "_react_light": { + "fontCharacter": "\\E07D", + "fontColor": "#498ba7" + }, + "_react": { + "fontCharacter": "\\E07D", + "fontColor": "#519aba" + }, + "_react_1_light": { + "fontCharacter": "\\E07D", + "fontColor": "#cc6d2e" + }, + "_react_1": { + "fontCharacter": "\\E07D", + "fontColor": "#e37933" + }, + "_reasonml_light": { + "fontCharacter": "\\E07E", + "fontColor": "#b8383d" + }, + "_reasonml": { + "fontCharacter": "\\E07E", + "fontColor": "#cc3e44" + }, + "_rescript_light": { + "fontCharacter": "\\E07F", + "fontColor": "#b8383d" + }, + "_rescript": { + "fontCharacter": "\\E07F", + "fontColor": "#cc3e44" + }, + "_rescript_1_light": { + "fontCharacter": "\\E07F", + "fontColor": "#dd4b78" + }, + "_rescript_1": { + "fontCharacter": "\\E07F", + "fontColor": "#f55385" + }, + "_rollup_light": { + "fontCharacter": "\\E080", + "fontColor": "#b8383d" + }, + "_rollup": { + "fontCharacter": "\\E080", + "fontColor": "#cc3e44" + }, + "_ruby_light": { + "fontCharacter": "\\E081", + "fontColor": "#b8383d" + }, + "_ruby": { + "fontCharacter": "\\E081", + "fontColor": "#cc3e44" + }, + "_rust_light": { + "fontCharacter": "\\E082", + "fontColor": "#627379" + }, + "_rust": { + "fontCharacter": "\\E082", + "fontColor": "#6d8086" + }, + "_salesforce_light": { + "fontCharacter": "\\E083", + "fontColor": "#498ba7" + }, + "_salesforce": { + "fontCharacter": "\\E083", + "fontColor": "#519aba" + }, + "_sass_light": { + "fontCharacter": "\\E084", + "fontColor": "#dd4b78" + }, + "_sass": { + "fontCharacter": "\\E084", + "fontColor": "#f55385" + }, + "_sbt_light": { + "fontCharacter": "\\E085", + "fontColor": "#498ba7" + }, + "_sbt": { + "fontCharacter": "\\E085", + "fontColor": "#519aba" + }, + "_scala_light": { + "fontCharacter": "\\E086", + "fontColor": "#b8383d" + }, + "_scala": { + "fontCharacter": "\\E086", + "fontColor": "#cc3e44" + }, + "_shell_light": { + "fontCharacter": "\\E089", + "fontColor": "#7fae42" + }, + "_shell": { + "fontCharacter": "\\E089", + "fontColor": "#8dc149" + }, + "_slim_light": { + "fontCharacter": "\\E08A", + "fontColor": "#cc6d2e" + }, + "_slim": { + "fontCharacter": "\\E08A", + "fontColor": "#e37933" + }, + "_smarty_light": { + "fontCharacter": "\\E08B", + "fontColor": "#b7b73b" + }, + "_smarty": { + "fontCharacter": "\\E08B", + "fontColor": "#cbcb41" + }, + "_spring_light": { + "fontCharacter": "\\E08C", + "fontColor": "#7fae42" + }, + "_spring": { + "fontCharacter": "\\E08C", + "fontColor": "#8dc149" + }, + "_stylelint_light": { + "fontCharacter": "\\E08D", + "fontColor": "#bfc2c1" + }, + "_stylelint": { + "fontCharacter": "\\E08D", + "fontColor": "#d4d7d6" + }, + "_stylelint_1_light": { + "fontCharacter": "\\E08D", + "fontColor": "#455155" + }, + "_stylelint_1": { + "fontCharacter": "\\E08D", + "fontColor": "#4d5a5e" + }, + "_stylus_light": { + "fontCharacter": "\\E08E", + "fontColor": "#7fae42" + }, + "_stylus": { + "fontCharacter": "\\E08E", + "fontColor": "#8dc149" + }, + "_sublime_light": { + "fontCharacter": "\\E08F", + "fontColor": "#cc6d2e" + }, + "_sublime": { + "fontCharacter": "\\E08F", + "fontColor": "#e37933" + }, + "_svelte_light": { + "fontCharacter": "\\E090", + "fontColor": "#b8383d" + }, + "_svelte": { + "fontCharacter": "\\E090", + "fontColor": "#cc3e44" + }, + "_svg_light": { + "fontCharacter": "\\E091", + "fontColor": "#9068b0" + }, + "_svg": { + "fontCharacter": "\\E091", + "fontColor": "#a074c4" + }, + "_svg_1_light": { + "fontCharacter": "\\E091", + "fontColor": "#498ba7" + }, + "_svg_1": { + "fontCharacter": "\\E091", + "fontColor": "#519aba" + }, + "_swift_light": { + "fontCharacter": "\\E092", + "fontColor": "#cc6d2e" + }, + "_swift": { + "fontCharacter": "\\E092", + "fontColor": "#e37933" + }, + "_terraform_light": { + "fontCharacter": "\\E093", + "fontColor": "#9068b0" + }, + "_terraform": { + "fontCharacter": "\\E093", + "fontColor": "#a074c4" + }, + "_tex_light": { + "fontCharacter": "\\E094", + "fontColor": "#498ba7" + }, + "_tex": { + "fontCharacter": "\\E094", + "fontColor": "#519aba" + }, + "_tex_1_light": { + "fontCharacter": "\\E094", + "fontColor": "#b7b73b" + }, + "_tex_1": { + "fontCharacter": "\\E094", + "fontColor": "#cbcb41" + }, + "_tex_2_light": { + "fontCharacter": "\\E094", + "fontColor": "#cc6d2e" + }, + "_tex_2": { + "fontCharacter": "\\E094", + "fontColor": "#e37933" + }, + "_tex_3_light": { + "fontCharacter": "\\E094", + "fontColor": "#bfc2c1" + }, + "_tex_3": { + "fontCharacter": "\\E094", + "fontColor": "#d4d7d6" + }, + "_todo": { + "fontCharacter": "\\E096" + }, + "_tsconfig_light": { + "fontCharacter": "\\E097", + "fontColor": "#498ba7" + }, + "_tsconfig": { + "fontCharacter": "\\E097", + "fontColor": "#519aba" + }, + "_twig_light": { + "fontCharacter": "\\E098", + "fontColor": "#7fae42" + }, + "_twig": { + "fontCharacter": "\\E098", + "fontColor": "#8dc149" + }, + "_typescript_light": { + "fontCharacter": "\\E099", + "fontColor": "#498ba7" + }, + "_typescript": { + "fontCharacter": "\\E099", + "fontColor": "#519aba" + }, + "_typescript_1_light": { + "fontCharacter": "\\E099", + "fontColor": "#cc6d2e" + }, + "_typescript_1": { + "fontCharacter": "\\E099", + "fontColor": "#e37933" + }, + "_vala_light": { + "fontCharacter": "\\E09A", + "fontColor": "#627379" + }, + "_vala": { + "fontCharacter": "\\E09A", + "fontColor": "#6d8086" + }, + "_video_light": { + "fontCharacter": "\\E09B", + "fontColor": "#dd4b78" + }, + "_video": { + "fontCharacter": "\\E09B", + "fontColor": "#f55385" + }, + "_vite_light": { + "fontCharacter": "\\E09C", + "fontColor": "#b7b73b" + }, + "_vite": { + "fontCharacter": "\\E09C", + "fontColor": "#cbcb41" + }, + "_vue_light": { + "fontCharacter": "\\E09D", + "fontColor": "#7fae42" + }, + "_vue": { + "fontCharacter": "\\E09D", + "fontColor": "#8dc149" + }, + "_wasm_light": { + "fontCharacter": "\\E09E", + "fontColor": "#9068b0" + }, + "_wasm": { + "fontCharacter": "\\E09E", + "fontColor": "#a074c4" + }, + "_wat_light": { + "fontCharacter": "\\E09F", + "fontColor": "#9068b0" + }, + "_wat": { + "fontCharacter": "\\E09F", + "fontColor": "#a074c4" + }, + "_webpack_light": { + "fontCharacter": "\\E0A0", + "fontColor": "#498ba7" + }, + "_webpack": { + "fontCharacter": "\\E0A0", + "fontColor": "#519aba" + }, + "_wgt_light": { + "fontCharacter": "\\E0A1", + "fontColor": "#498ba7" + }, + "_wgt": { + "fontCharacter": "\\E0A1", + "fontColor": "#519aba" + }, + "_windows_light": { + "fontCharacter": "\\E0A2", + "fontColor": "#498ba7" + }, + "_windows": { + "fontCharacter": "\\E0A2", + "fontColor": "#519aba" + }, + "_word_light": { + "fontCharacter": "\\E0A3", + "fontColor": "#498ba7" + }, + "_word": { + "fontCharacter": "\\E0A3", + "fontColor": "#519aba" + }, + "_xls_light": { + "fontCharacter": "\\E0A4", + "fontColor": "#7fae42" + }, + "_xls": { + "fontCharacter": "\\E0A4", + "fontColor": "#8dc149" + }, + "_xml_light": { + "fontCharacter": "\\E0A5", + "fontColor": "#cc6d2e" + }, + "_xml": { + "fontCharacter": "\\E0A5", + "fontColor": "#e37933" + }, + "_yarn_light": { + "fontCharacter": "\\E0A6", + "fontColor": "#498ba7" + }, + "_yarn": { + "fontCharacter": "\\E0A6", + "fontColor": "#519aba" + }, + "_yml_light": { + "fontCharacter": "\\E0A7", + "fontColor": "#9068b0" + }, + "_yml": { + "fontCharacter": "\\E0A7", + "fontColor": "#a074c4" + }, + "_zig_light": { + "fontCharacter": "\\E0A8", + "fontColor": "#cc6d2e" + }, + "_zig": { + "fontCharacter": "\\E0A8", + "fontColor": "#e37933" + }, + "_zip_light": { + "fontCharacter": "\\E0A9", + "fontColor": "#b8383d" + }, + "_zip": { + "fontCharacter": "\\E0A9", + "fontColor": "#cc3e44" + }, + "_zip_1_light": { + "fontCharacter": "\\E0A9", + "fontColor": "#627379" + }, + "_zip_1": { + "fontCharacter": "\\E0A9", + "fontColor": "#6d8086" + } + }, + "file": "_default", + "fileExtensions": { + "bsl": "_bsl", + "mdo": "_mdo", + "cls": "_salesforce", + "apex": "_salesforce", + "asm": "_asm", + "s": "_asm", + "bicep": "_bicep", + "bzl": "_bazel", + "bazel": "_bazel", + "build": "_bazel", + "workspace": "_bazel", + "bazelignore": "_bazel", + "bazelversion": "_bazel", + "h": "_c_1", + "aspx": "_html", + "ascx": "_html_1", + "asax": "_html_2", + "master": "_html_2", + "hh": "_cpp_1", + "hpp": "_cpp_1", + "hxx": "_cpp_1", + "h++": "_cpp_1", + "edn": "_clojure_1", + "cfc": "_coldfusion", + "cfm": "_coldfusion", + "litcoffee": "_coffee", + "config": "_config", + "cr": "_crystal", + "ecr": "_crystal_embedded", + "slang": "_crystal_embedded", + "cson": "_json", + "css.map": "_css", + "sss": "_css", + "csv": "_csv", + "xls": "_xls", + "xlsx": "_xls", + "cuh": "_cu_1", + "hu": "_cu_1", + "cake": "_cake", + "ctp": "_cake_php", + "d": "_d", + "doc": "_word", + "docx": "_word", + "ejs": "_ejs", + "ex": "_elixir", + "exs": "_elixir_script", + "elm": "_elm", + "ico": "_favicon", + "gitconfig": "_git", + "gitkeep": "_git", + "gitattributes": "_git", + "gitmodules": "_git", + "slide": "_go", + "article": "_go", + "gd": "_godot", + "godot": "_godot_1", + "tres": "_godot_2", + "tscn": "_godot_3", + "gradle": "_gradle", + "gsp": "_grails", + "gql": "_graphql", + "graphql": "_graphql", + "graphqls": "_graphql", + "hack": "_hacklang", + "haml": "_haml", + "hs": "_haskell", + "lhs": "_haskell", + "hx": "_haxe", + "hxs": "_haxe_1", + "hxp": "_haxe_2", + "hxml": "_haxe_3", + "jade": "_jade", + "class": "_java_1", + "classpath": "_java", + "js.map": "_javascript", + "cjs.map": "_javascript", + "mjs.map": "_javascript", + "spec.js": "_javascript_1", + "spec.cjs": "_javascript_1", + "spec.mjs": "_javascript_1", + "test.js": "_javascript_1", + "test.cjs": "_javascript_1", + "test.mjs": "_javascript_1", + "es": "_javascript", + "es5": "_javascript", + "es7": "_javascript", + "jinja": "_jinja", + "jinja2": "_jinja", + "kt": "_kotlin", + "kts": "_kotlin", + "liquid": "_liquid", + "ls": "_livescript", + "argdown": "_argdown", + "ad": "_argdown", + "mustache": "_mustache", + "stache": "_mustache", + "nim": "_nim", + "nims": "_nim", + "github-issues": "_github", + "ipynb": "_notebook", + "njk": "_nunjucks", + "nunjucks": "_nunjucks", + "nunjs": "_nunjucks", + "nunj": "_nunjucks", + "njs": "_nunjucks", + "nj": "_nunjucks", + "npm-debug.log": "_npm", + "npmignore": "_npm_1", + "npmrc": "_npm_1", + "ml": "_ocaml", + "mli": "_ocaml", + "cmx": "_ocaml", + "cmxa": "_ocaml", + "odata": "_odata", + "php.inc": "_php", + "pipeline": "_pipeline", + "pddl": "_pddl", + "plan": "_plan", + "happenings": "_happenings", + "prisma": "_prisma", + "pp": "_puppet", + "epp": "_puppet", + "purs": "_purescript", + "spec.jsx": "_react_1", + "test.jsx": "_react_1", + "cjsx": "_react", + "tsx": "_react_1", + "spec.tsx": "_react_1", + "test.tsx": "_react_1", + "re": "_reasonml", + "res": "_rescript", + "resi": "_rescript_1", + "r": "_R", + "rmd": "_R", + "erb": "_html_erb", + "erb.html": "_html_erb", + "html.erb": "_html_erb", + "sass": "_sass", + "springbeans": "_spring", + "slim": "_slim", + "smarty.tpl": "_smarty", + "tpl": "_smarty", + "sbt": "_sbt", + "scala": "_scala", + "sol": "_ethereum", + "styl": "_stylus", + "svelte": "_svelte", + "soql": "_db_1", + "tf": "_terraform", + "tf.json": "_terraform", + "tfvars": "_terraform", + "tfvars.json": "_terraform", + "dtx": "_tex_2", + "ins": "_tex_3", + "toml": "_config", + "twig": "_twig", + "ts": "_typescript", + "spec.ts": "_typescript_1", + "test.ts": "_typescript_1", + "vala": "_vala", + "vapi": "_vala", + "component": "_html_3", + "vue": "_vue", + "wasm": "_wasm", + "wat": "_wat", + "pro": "_prolog", + "zig": "_zig", + "jar": "_zip", + "zip": "_zip_1", + "wgt": "_wgt", + "ai": "_illustrator", + "psd": "_photoshop", + "pdf": "_pdf", + "eot": "_font", + "ttf": "_font", + "woff": "_font", + "woff2": "_font", + "otf": "_font", + "avif": "_image", + "gif": "_image", + "jpg": "_image", + "jpeg": "_image", + "png": "_image", + "pxm": "_image", + "svg": "_svg", + "svgx": "_image", + "tiff": "_image", + "webp": "_image", + "sublime-project": "_sublime", + "sublime-workspace": "_sublime", + "mov": "_video", + "ogv": "_video", + "webm": "_video", + "avi": "_video", + "mpg": "_video", + "mp4": "_video", + "mp3": "_audio", + "ogg": "_audio", + "wav": "_audio", + "flac": "_audio", + "3ds": "_svg_1", + "3dm": "_svg_1", + "stl": "_svg_1", + "obj": "_svg_1", + "dae": "_svg_1", + "babelrc": "_babel", + "babelrc.js": "_babel", + "babelrc.cjs": "_babel", + "bazelrc": "_bazel_1", + "bowerrc": "_bower", + "dockerignore": "_docker_1", + "codeclimate.yml": "_code-climate", + "eslintrc": "_eslint", + "eslintrc.js": "_eslint", + "eslintrc.cjs": "_eslint", + "eslintrc.yaml": "_eslint", + "eslintrc.yml": "_eslint", + "eslintrc.json": "_eslint", + "eslintignore": "_eslint_1", + "firebaserc": "_firebase", + "gitlab-ci.yml": "_gitlab", + "jshintrc": "_javascript_2", + "jscsrc": "_javascript_2", + "stylelintrc": "_stylelint", + "stylelintrc.json": "_stylelint", + "stylelintrc.yaml": "_stylelint", + "stylelintrc.yml": "_stylelint", + "stylelintrc.js": "_stylelint", + "stylelintignore": "_stylelint_1", + "direnv": "_config", + "static": "_config", + "slugignore": "_config", + "tmp": "_clock_1", + "htaccess": "_config", + "key": "_lock", + "cert": "_lock", + "cer": "_lock", + "crt": "_lock", + "pem": "_lock", + "ds_store": "_ignored" + }, + "fileNames": { + "mix": "_hex", + "karma.conf.js": "_karma", + "karma.conf.cjs": "_karma", + "karma.conf.mjs": "_karma", + "karma.conf.coffee": "_karma", + "readme.md": "_info", + "readme.txt": "_info", + "readme": "_info", + "changelog.md": "_clock", + "changelog.txt": "_clock", + "changelog": "_clock", + "changes.md": "_clock", + "changes.txt": "_clock", + "changes": "_clock", + "version.md": "_clock", + "version.txt": "_clock", + "version": "_clock", + "mvnw": "_maven", + "pom.xml": "_maven", + "tsconfig.json": "_tsconfig", + "vite.config.js": "_vite", + "vite.config.ts": "_vite", + "vite.config.mjs": "_vite", + "vite.config.mts": "_vite", + "vite.config.cjs": "_vite", + "vite.config.cts": "_vite", + "swagger.json": "_json_1", + "swagger.yml": "_json_1", + "swagger.yaml": "_json_1", + "mime.types": "_config", + "jenkinsfile": "_jenkins", + "babel.config.js": "_babel", + "babel.config.json": "_babel", + "babel.config.cjs": "_babel", + "build": "_bazel", + "build.bazel": "_bazel", + "workspace": "_bazel", + "workspace.bazel": "_bazel", + "bower.json": "_bower", + "docker-healthcheck": "_docker_2", + "eslint.config.js": "_eslint", + "firebase.json": "_firebase", + "geckodriver": "_firefox", + "gruntfile.js": "_grunt", + "gruntfile.babel.js": "_grunt", + "gruntfile.coffee": "_grunt", + "gulpfile": "_gulp", + "gulpfile.js": "_gulp", + "ionic.config.json": "_ionic", + "ionic.project": "_ionic", + "platformio.ini": "_platformio", + "rollup.config.js": "_rollup", + "sass-lint.yml": "_sass", + "stylelint.config.js": "_stylelint", + "stylelint.config.cjs": "_stylelint", + "stylelint.config.mjs": "_stylelint", + "yarn.clean": "_yarn", + "yarn.lock": "_yarn", + "webpack.config.js": "_webpack", + "webpack.config.cjs": "_webpack", + "webpack.config.mjs": "_webpack", + "webpack.config.ts": "_webpack", + "webpack.config.build.js": "_webpack", + "webpack.config.build.cjs": "_webpack", + "webpack.config.build.mjs": "_webpack", + "webpack.config.build.ts": "_webpack", + "webpack.common.js": "_webpack", + "webpack.common.cjs": "_webpack", + "webpack.common.mjs": "_webpack", + "webpack.common.ts": "_webpack", + "webpack.dev.js": "_webpack", + "webpack.dev.cjs": "_webpack", + "webpack.dev.mjs": "_webpack", + "webpack.dev.ts": "_webpack", + "webpack.prod.js": "_webpack", + "webpack.prod.cjs": "_webpack", + "webpack.prod.mjs": "_webpack", + "webpack.prod.ts": "_webpack", + "license": "_license", + "licence": "_license", + "license.txt": "_license", + "licence.txt": "_license", + "license.md": "_license", + "licence.md": "_license", + "copying": "_license", + "copying.txt": "_license", + "copying.md": "_license", + "compiling": "_license_1", + "compiling.txt": "_license_1", + "compiling.md": "_license_1", + "contributing": "_license_2", + "contributing.txt": "_license_2", + "contributing.md": "_license_2", + "qmakefile": "_makefile_1", + "omakefile": "_makefile_2", + "cmakelists.txt": "_makefile_3", + "procfile": "_heroku", + "todo": "_todo", + "todo.txt": "_todo", + "todo.md": "_todo", + "npm-debug.log": "_npm_ignored" + }, + "languageIds": { + "bat": "_windows", + "clojure": "_clojure", + "coffeescript": "_coffee", + "jsonc": "_json", + "json": "_json", + "c": "_c", + "cpp": "_cpp", + "cuda-cpp": "_cu", + "csharp": "_c-sharp", + "css": "_css", + "dart": "_dart", + "dockerfile": "_docker", + "dotenv": "_config", + "ignore": "_git", + "fsharp": "_f-sharp", + "git-commit": "_git", + "go": "_go2", + "groovy": "_grails", + "handlebars": "_mustache", + "html": "_html_3", + "properties": "_config", + "java": "_java", + "javascriptreact": "_react", + "javascript": "_javascript", + "julia": "_julia", + "tex": "_tex_1", + "latex": "_tex", + "less": "_less", + "lua": "_lua", + "makefile": "_makefile", + "markdown": "_markdown", + "objective-c": "_c_2", + "objective-cpp": "_cpp_2", + "perl": "_perl", + "php": "_php", + "powershell": "_powershell", + "jade": "_pug", + "python": "_python", + "r": "_R", + "razor": "_html", + "ruby": "_ruby", + "rust": "_rust", + "scss": "_sass", + "search-result": "_code-search", + "shellscript": "_shell", + "sql": "_db", + "swift": "_swift", + "typescript": "_typescript", + "typescriptreact": "_react", + "xml": "_xml", + "dockercompose": "_docker_3", + "yaml": "_yml", + "argdown": "_argdown", + "bicep": "_bicep", + "elixir": "_elixir", + "elm": "_elm", + "erb": "_html_erb", + "github-issues": "_github", + "gradle": "_gradle", + "godot": "_godot", + "haml": "_haml", + "haskell": "_haskell", + "haxe": "_haxe", + "jinja": "_jinja", + "kotlin": "_kotlin", + "mustache": "_mustache", + "nunjucks": "_nunjucks", + "ocaml": "_ocaml", + "rescript": "_rescript", + "sass": "_sass", + "stylus": "_stylus", + "terraform": "_terraform", + "todo": "_todo", + "vala": "_vala", + "vue": "_vue", + "jsonl": "_json", + "postcss": "_css", + "django-html": "_html_3", + "blade": "_php" + }, + "light": { + "file": "_default_light", + "fileExtensions": { + "bsl": "_bsl_light", + "mdo": "_mdo_light", + "cls": "_salesforce_light", + "apex": "_salesforce_light", + "asm": "_asm_light", + "s": "_asm_light", + "bicep": "_bicep_light", + "bzl": "_bazel_light", + "bazel": "_bazel_light", + "build": "_bazel_light", + "workspace": "_bazel_light", + "bazelignore": "_bazel_light", + "bazelversion": "_bazel_light", + "h": "_c_1_light", + "aspx": "_html_light", + "ascx": "_html_1_light", + "asax": "_html_2_light", + "master": "_html_2_light", + "hh": "_cpp_1_light", + "hpp": "_cpp_1_light", + "hxx": "_cpp_1_light", + "h++": "_cpp_1_light", + "edn": "_clojure_1_light", + "cfc": "_coldfusion_light", + "cfm": "_coldfusion_light", + "litcoffee": "_coffee_light", + "config": "_config_light", + "cr": "_crystal_light", + "ecr": "_crystal_embedded_light", + "slang": "_crystal_embedded_light", + "cson": "_json_light", + "css.map": "_css_light", + "sss": "_css_light", + "csv": "_csv_light", + "xls": "_xls_light", + "xlsx": "_xls_light", + "cuh": "_cu_1_light", + "hu": "_cu_1_light", + "cake": "_cake_light", + "ctp": "_cake_php_light", + "d": "_d_light", + "doc": "_word_light", + "docx": "_word_light", + "ejs": "_ejs_light", + "ex": "_elixir_light", + "exs": "_elixir_script_light", + "elm": "_elm_light", + "ico": "_favicon_light", + "gitconfig": "_git_light", + "gitkeep": "_git_light", + "gitattributes": "_git_light", + "gitmodules": "_git_light", + "slide": "_go_light", + "article": "_go_light", + "gd": "_godot_light", + "godot": "_godot_1_light", + "tres": "_godot_2_light", + "tscn": "_godot_3_light", + "gradle": "_gradle_light", + "gsp": "_grails_light", + "gql": "_graphql_light", + "graphql": "_graphql_light", + "graphqls": "_graphql_light", + "hack": "_hacklang_light", + "haml": "_haml_light", + "hs": "_haskell_light", + "lhs": "_haskell_light", + "hx": "_haxe_light", + "hxs": "_haxe_1_light", + "hxp": "_haxe_2_light", + "hxml": "_haxe_3_light", + "jade": "_jade_light", + "class": "_java_1_light", + "classpath": "_java_light", + "js.map": "_javascript_light", + "cjs.map": "_javascript_light", + "mjs.map": "_javascript_light", + "spec.js": "_javascript_1_light", + "spec.cjs": "_javascript_1_light", + "spec.mjs": "_javascript_1_light", + "test.js": "_javascript_1_light", + "test.cjs": "_javascript_1_light", + "test.mjs": "_javascript_1_light", + "es": "_javascript_light", + "es5": "_javascript_light", + "es7": "_javascript_light", + "jinja": "_jinja_light", + "jinja2": "_jinja_light", + "kt": "_kotlin_light", + "kts": "_kotlin_light", + "liquid": "_liquid_light", + "ls": "_livescript_light", + "argdown": "_argdown_light", + "ad": "_argdown_light", + "mustache": "_mustache_light", + "stache": "_mustache_light", + "nim": "_nim_light", + "nims": "_nim_light", + "github-issues": "_github_light", + "ipynb": "_notebook_light", + "njk": "_nunjucks_light", + "nunjucks": "_nunjucks_light", + "nunjs": "_nunjucks_light", + "nunj": "_nunjucks_light", + "njs": "_nunjucks_light", + "nj": "_nunjucks_light", + "npm-debug.log": "_npm_light", + "npmignore": "_npm_1_light", + "npmrc": "_npm_1_light", + "ml": "_ocaml_light", + "mli": "_ocaml_light", + "cmx": "_ocaml_light", + "cmxa": "_ocaml_light", + "odata": "_odata_light", + "php.inc": "_php_light", + "pipeline": "_pipeline_light", + "pddl": "_pddl_light", + "plan": "_plan_light", + "happenings": "_happenings_light", + "prisma": "_prisma_light", + "pp": "_puppet_light", + "epp": "_puppet_light", + "purs": "_purescript_light", + "spec.jsx": "_react_1_light", + "test.jsx": "_react_1_light", + "cjsx": "_react_light", + "spec.tsx": "_react_1_light", + "test.tsx": "_react_1_light", + "re": "_reasonml_light", + "res": "_rescript_light", + "resi": "_rescript_1_light", + "r": "_R_light", + "rmd": "_R_light", + "erb": "_html_erb_light", + "erb.html": "_html_erb_light", + "html.erb": "_html_erb_light", + "sass": "_sass_light", + "springbeans": "_spring_light", + "slim": "_slim_light", + "smarty.tpl": "_smarty_light", + "tpl": "_smarty_light", + "sbt": "_sbt_light", + "scala": "_scala_light", + "sol": "_ethereum_light", + "styl": "_stylus_light", + "svelte": "_svelte_light", + "soql": "_db_1_light", + "tf": "_terraform_light", + "tf.json": "_terraform_light", + "tfvars": "_terraform_light", + "tfvars.json": "_terraform_light", + "dtx": "_tex_2_light", + "ins": "_tex_3_light", + "toml": "_config_light", + "twig": "_twig_light", + "spec.ts": "_typescript_1_light", + "test.ts": "_typescript_1_light", + "vala": "_vala_light", + "vapi": "_vala_light", + "component": "_html_3_light", + "vue": "_vue_light", + "wasm": "_wasm_light", + "wat": "_wat_light", + "pro": "_prolog_light", + "zig": "_zig_light", + "jar": "_zip_light", + "zip": "_zip_1_light", + "wgt": "_wgt_light", + "ai": "_illustrator_light", + "psd": "_photoshop_light", + "pdf": "_pdf_light", + "eot": "_font_light", + "ttf": "_font_light", + "woff": "_font_light", + "woff2": "_font_light", + "otf": "_font_light", + "avif": "_image_light", + "gif": "_image_light", + "jpg": "_image_light", + "jpeg": "_image_light", + "png": "_image_light", + "pxm": "_image_light", + "svg": "_svg_light", + "svgx": "_image_light", + "tiff": "_image_light", + "webp": "_image_light", + "sublime-project": "_sublime_light", + "sublime-workspace": "_sublime_light", + "mov": "_video_light", + "ogv": "_video_light", + "webm": "_video_light", + "avi": "_video_light", + "mpg": "_video_light", + "mp4": "_video_light", + "mp3": "_audio_light", + "ogg": "_audio_light", + "wav": "_audio_light", + "flac": "_audio_light", + "3ds": "_svg_1_light", + "3dm": "_svg_1_light", + "stl": "_svg_1_light", + "obj": "_svg_1_light", + "dae": "_svg_1_light", + "babelrc": "_babel_light", + "babelrc.js": "_babel_light", + "babelrc.cjs": "_babel_light", + "bazelrc": "_bazel_1_light", + "bowerrc": "_bower_light", + "dockerignore": "_docker_1_light", + "codeclimate.yml": "_code-climate_light", + "eslintrc": "_eslint_light", + "eslintrc.js": "_eslint_light", + "eslintrc.cjs": "_eslint_light", + "eslintrc.yaml": "_eslint_light", + "eslintrc.yml": "_eslint_light", + "eslintrc.json": "_eslint_light", + "eslintignore": "_eslint_1_light", + "firebaserc": "_firebase_light", + "gitlab-ci.yml": "_gitlab_light", + "jshintrc": "_javascript_2_light", + "jscsrc": "_javascript_2_light", + "stylelintrc": "_stylelint_light", + "stylelintrc.json": "_stylelint_light", + "stylelintrc.yaml": "_stylelint_light", + "stylelintrc.yml": "_stylelint_light", + "stylelintrc.js": "_stylelint_light", + "stylelintignore": "_stylelint_1_light", + "direnv": "_config_light", + "static": "_config_light", + "slugignore": "_config_light", + "tmp": "_clock_1_light", + "htaccess": "_config_light", + "key": "_lock_light", + "cert": "_lock_light", + "cer": "_lock_light", + "crt": "_lock_light", + "pem": "_lock_light", + "ds_store": "_ignored_light" + }, + "languageIds": { + "bat": "_windows_light", + "clojure": "_clojure_light", + "coffeescript": "_coffee_light", + "jsonc": "_json_light", + "json": "_json_light", + "c": "_c_light", + "cpp": "_cpp_light", + "cuda-cpp": "_cu_light", + "csharp": "_c-sharp_light", + "css": "_css_light", + "dart": "_dart_light", + "dockerfile": "_docker_light", + "dotenv": "_config_light", + "ignore": "_git_light", + "fsharp": "_f-sharp_light", + "git-commit": "_git_light", + "go": "_go2_light", + "groovy": "_grails_light", + "handlebars": "_mustache_light", + "html": "_html_3_light", + "properties": "_config_light", + "java": "_java_light", + "javascriptreact": "_react_light", + "javascript": "_javascript_light", + "julia": "_julia_light", + "tex": "_tex_1_light", + "latex": "_tex_light", + "less": "_less_light", + "lua": "_lua_light", + "makefile": "_makefile_light", + "markdown": "_markdown_light", + "objective-c": "_c_2_light", + "objective-cpp": "_cpp_2_light", + "perl": "_perl_light", + "php": "_php_light", + "powershell": "_powershell_light", + "jade": "_pug_light", + "python": "_python_light", + "r": "_R_light", + "razor": "_html_light", + "ruby": "_ruby_light", + "rust": "_rust_light", + "scss": "_sass_light", + "search-result": "_code-search_light", + "shellscript": "_shell_light", + "sql": "_db_light", + "swift": "_swift_light", + "typescript": "_typescript_light", + "typescriptreact": "_react_light", + "xml": "_xml_light", + "dockercompose": "_docker_3_light", + "yaml": "_yml_light", + "argdown": "_argdown_light", + "bicep": "_bicep_light", + "elixir": "_elixir_light", + "elm": "_elm_light", + "erb": "_html_erb_light", + "github-issues": "_github_light", + "gradle": "_gradle_light", + "godot": "_godot_light", + "haml": "_haml_light", + "haskell": "_haskell_light", + "haxe": "_haxe_light", + "jinja": "_jinja_light", + "kotlin": "_kotlin_light", + "mustache": "_mustache_light", + "nunjucks": "_nunjucks_light", + "ocaml": "_ocaml_light", + "rescript": "_rescript_light", + "sass": "_sass_light", + "stylus": "_stylus_light", + "terraform": "_terraform_light", + "vala": "_vala_light", + "vue": "_vue_light", + "jsonl": "_json_light", + "postcss": "_css_light", + "django-html": "_html_3_light", + "blade": "_php_light" + }, + "fileNames": { + "mix": "_hex_light", + "karma.conf.js": "_karma_light", + "karma.conf.cjs": "_karma_light", + "karma.conf.mjs": "_karma_light", + "karma.conf.coffee": "_karma_light", + "readme.md": "_info_light", + "readme.txt": "_info_light", + "readme": "_info_light", + "changelog.md": "_clock_light", + "changelog.txt": "_clock_light", + "changelog": "_clock_light", + "changes.md": "_clock_light", + "changes.txt": "_clock_light", + "changes": "_clock_light", + "version.md": "_clock_light", + "version.txt": "_clock_light", + "version": "_clock_light", + "mvnw": "_maven_light", + "pom.xml": "_maven_light", + "tsconfig.json": "_tsconfig_light", + "vite.config.js": "_vite_light", + "vite.config.ts": "_vite_light", + "vite.config.mjs": "_vite_light", + "vite.config.mts": "_vite_light", + "vite.config.cjs": "_vite_light", + "vite.config.cts": "_vite_light", + "swagger.json": "_json_1_light", + "swagger.yml": "_json_1_light", + "swagger.yaml": "_json_1_light", + "mime.types": "_config_light", + "jenkinsfile": "_jenkins_light", + "babel.config.js": "_babel_light", + "babel.config.json": "_babel_light", + "babel.config.cjs": "_babel_light", + "build": "_bazel_light", + "build.bazel": "_bazel_light", + "workspace": "_bazel_light", + "workspace.bazel": "_bazel_light", + "bower.json": "_bower_light", + "docker-healthcheck": "_docker_2_light", + "eslint.config.js": "_eslint_light", + "firebase.json": "_firebase_light", + "geckodriver": "_firefox_light", + "gruntfile.js": "_grunt_light", + "gruntfile.babel.js": "_grunt_light", + "gruntfile.coffee": "_grunt_light", + "gulpfile": "_gulp_light", + "gulpfile.js": "_gulp_light", + "ionic.config.json": "_ionic_light", + "ionic.project": "_ionic_light", + "platformio.ini": "_platformio_light", + "rollup.config.js": "_rollup_light", + "sass-lint.yml": "_sass_light", + "stylelint.config.js": "_stylelint_light", + "stylelint.config.cjs": "_stylelint_light", + "stylelint.config.mjs": "_stylelint_light", + "yarn.clean": "_yarn_light", + "yarn.lock": "_yarn_light", + "webpack.config.js": "_webpack_light", + "webpack.config.cjs": "_webpack_light", + "webpack.config.mjs": "_webpack_light", + "webpack.config.ts": "_webpack_light", + "webpack.config.build.js": "_webpack_light", + "webpack.config.build.cjs": "_webpack_light", + "webpack.config.build.mjs": "_webpack_light", + "webpack.config.build.ts": "_webpack_light", + "webpack.common.js": "_webpack_light", + "webpack.common.cjs": "_webpack_light", + "webpack.common.mjs": "_webpack_light", + "webpack.common.ts": "_webpack_light", + "webpack.dev.js": "_webpack_light", + "webpack.dev.cjs": "_webpack_light", + "webpack.dev.mjs": "_webpack_light", + "webpack.dev.ts": "_webpack_light", + "webpack.prod.js": "_webpack_light", + "webpack.prod.cjs": "_webpack_light", + "webpack.prod.mjs": "_webpack_light", + "webpack.prod.ts": "_webpack_light", + "license": "_license_light", + "licence": "_license_light", + "license.txt": "_license_light", + "licence.txt": "_license_light", + "license.md": "_license_light", + "licence.md": "_license_light", + "copying": "_license_light", + "copying.txt": "_license_light", + "copying.md": "_license_light", + "compiling": "_license_1_light", + "compiling.txt": "_license_1_light", + "compiling.md": "_license_1_light", + "contributing": "_license_2_light", + "contributing.txt": "_license_2_light", + "contributing.md": "_license_2_light", + "qmakefile": "_makefile_1_light", + "omakefile": "_makefile_2_light", + "cmakelists.txt": "_makefile_3_light", + "procfile": "_heroku_light", + "npm-debug.log": "_npm_ignored_light" + } + }, + "version": "https://github.com/jesseweed/seti-ui/commit/2d6c5e68b4ded73c92dac291845ee44e1182d511" +} diff --git a/apps/web/public/fonts/seti/seti.woff b/apps/web/public/fonts/seti/seti.woff new file mode 100644 index 0000000000..6a8a3da7cf Binary files /dev/null and b/apps/web/public/fonts/seti/seti.woff differ