From 616685c9b2ba02df2b8cc0b396fbbb3d6b37487b Mon Sep 17 00:00:00 2001 From: Isaac Johnson Date: Tue, 2 Jun 2026 17:31:24 -0500 Subject: [PATCH] cool boot animation, done in WSL --- README.md | 85 ++++++++++++++++++ SYSTEM.md | 226 ++++++++++++++++++++++++++++++++++++++++++++++++ ascii-art-2.txt | 55 ++++++++++++ ascii-art.txt | 55 ++++++++++++ src/tui.ts | 205 +++++++++++++++++++++++++++++++++++++++++-- src/types.ts | 2 +- 6 files changed, 622 insertions(+), 6 deletions(-) create mode 100644 README.md create mode 100644 SYSTEM.md create mode 100755 ascii-art-2.txt create mode 100755 ascii-art.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..0642fa3 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# Gitea TUI (fjtui) + +A premium interactive CLI TUI Dashboard for exploring Gitea and Forgejo issues directly from your terminal. + +## Features + +- **Interactive TUI:** A full-featured terminal user interface using Node.js. +- **Multiple Screens:** + - **Setup Wizard:** Easy configuration and connection testing. + - **Repository Picker:** Browse and select repositories seamlessly. + - **Issue List:** View, filter, sort, and search issues in a repository. + - **Issue Details:** Read full issue descriptions and comments in a dedicated view. + - **Create Issue:** Draft and submit new issues directly from the terminal. + - **Add Comment:** Participate in issue discussions seamlessly. +- **Configuration Management:** Save your connection settings and tokens securely for future sessions. +- **Support for Forgejo & Gitea:** Built to integrate smoothly with Forgejo and Gitea APIs. + +## Installation + +Ensure you have Node.js (v20+) installed. + +Clone the repository and install dependencies: + +```bash +git clone fjtui +cd fjtui +npm install +``` + +Build the project: + +```bash +npm run build +``` + +Link globally (optional): + +```bash +npm link +``` +You can then run the tool anywhere using `gitea-tui`. + +## Usage + +You can start the interactive setup wizard by simply running: + +```bash +npm start +``` +Or, if installed globally: +```bash +gitea-tui +``` + +### Direct Connection + +You can also use command-line options to connect directly and bypass the setup wizard: + +```bash +npm start -- --url "https://forgejo.example.com" --userid "myuser" --repo "myuser/myrepo" --token "my-access-token" +``` + +### Command-Line Options + +- `-u, --url `: Forgejo/Gitea instance base URL (e.g. `https://forgejo.freshbrewed.science`) +- `-i, --userid `: Forgejo/Gitea user ID / username +- `-r, --repo `: Repository path (e.g. `owner/repo`) +- `-t, --token `: Personal Access Token (optional for public repositories) +- `-s, --save`: Save the provided settings to the global config file (`~/.config/fjtui/fjtui.json`) + +## Configuration + +Settings can be saved interactively via the Setup Wizard or by passing the `-s` or `--save` flag in the CLI. The application configuration file is stored safely at `~/.config/fjtui/fjtui.json`. + +## Development + +- `npm run build`: Compiles the TypeScript code to JavaScript in the `dist/` directory. +- `npm start`: Runs the built `dist/index.js` file. + +## Technologies Used + +- **TypeScript** / **Node.js** +- **Commander:** Command-line argument parsing. +- **Chalk:** Terminal styling. +- **Axios:** API interactions. diff --git a/SYSTEM.md b/SYSTEM.md new file mode 100644 index 0000000..b444c2b --- /dev/null +++ b/SYSTEM.md @@ -0,0 +1,226 @@ +# Gitea TUI (fjtui) System Architecture + +This document provides a comprehensive overview of the Gitea/Forgejo TUI Dashboard's system architecture, component layout, and user flows. It uses **Mermaid.js** diagrams to detail the application structure and page lifecycle transitions. + +--- + +## 1. High-Level Architecture + +The Gitea TUI client is a modular CLI application written in TypeScript and Node.js. It operates as a local state machine that communicates with a Gitea or Forgejo instance REST API over HTTPS. + +```mermaid +graph TD + subgraph Client ["Gitea TUI Application Context"] + EP["index.ts (CLI Entry Point)"] + ConfigManager["config.ts (Configuration Manager)"] + TuiEngine["tui.ts (TuiEngine Controller)"] + APILayer["api.ts (API Service Layer)"] + Types["types.ts (State & Entity Types)"] + end + + subgraph External ["External Resources"] + LocalConfig["Local Config (./fjtui.json)"] + GlobalConfig["Global Config (~/.config/fjtui/fjtui.json)"] + GiteaAPI["Gitea/Forgejo Instance REST API"] + end + + subgraph IO ["Terminal User Interface"] + Stdin["stdin (Raw Keypress Listeners)"] + Stdout["stdout (Chalk Styled Rendering)"] + end + + %% Key Relationships + EP -->|Reads Config / Parses Args| ConfigManager + EP -->|Direct Connect Check| APILayer + EP -->|Initializes & Starts| TuiEngine + + ConfigManager <-->|Read / Write| LocalConfig + ConfigManager <-->|Read / Write| GlobalConfig + + TuiEngine -->|Subscribes to Events| Stdin + TuiEngine -->|Paints Screens| Stdout + TuiEngine -->|Fetches & Mutates Data| APILayer + TuiEngine -->|Saves Config| ConfigManager + + APILayer -->|HTTP / REST Requests (Axios)| GiteaAPI + + TuiEngine -.->|Operates on AppState| Types +``` + +--- + +## 2. Screen & Page Transition Flow + +The terminal application operates as a single-page terminal application (SPTA) with a central screen router state. Below is the transition flow (state machine) representing how keypress actions route the user between screens. + +```mermaid +stateDiagram-v2 + [*] --> Launch : Run gitea-tui (Bootstrap or Direct Connect) + Launch --> Setup : No config / finished or skipped + Launch --> List : Config exists / direct connect (finished or skipped) + + state Setup { + [*] --> EnteringCredentials + EnteringCredentials --> Validating : Enter/Submit Form + Validating --> EnteringCredentials : Connection Error + } + + Setup --> RepoPicker : Validated & Repositories Loaded + + state RepoPicker { + [*] --> BrowsingRepos + BrowsingRepos --> BrowsingRepos : Filter / Search + } + + RepoPicker --> Setup : Escape (Go Back) + RepoPicker --> List : Select Repo (Enter) & Load Issues + + state List { + [*] --> DisplayingIssues + DisplayingIssues --> DisplayingIssues : Toggle Filters / Sort / Paginate / Search + } + + List --> Setup : Change Connection ('o' key) + List --> CreateIssue : Create Issue ('c' key) + List --> Details : View Issue Details (Enter) + + state CreateIssue { + [*] --> FillingIssueForm + FillingIssueForm --> SavingIssue : Ctrl+S (Submit) + SavingIssue --> DisplayingIssues : Success (List reloads) + SavingIssue --> FillingIssueForm : Failure (Shows error) + } + CreateIssue --> List : Escape (Cancel) + + state Details { + [*] --> ViewingDetails + ViewingDetails --> ViewingDetails : Scroll description & comments + } + + Details --> List : Escape / Backspace / Q (Go Back) + Details --> Setup : Change Connection ('o' key) + Details --> AddComment : Add Comment ('c' key) + Details --> EditIssue : Edit Issue ('e' key) + Details --> AddTime : Add Tracked Time ('t' key) + Details --> SetAssignees : Set Assignees ('a' key) + Details --> ConfirmStateChange : Toggle Issue State ('x' key) + + state AddComment { + [*] --> EnteringComment + EnteringComment --> SubmittingComment : Ctrl+S (Submit) + SubmittingComment --> ViewingDetails : Success (Comments reload) + SubmittingComment --> EnteringComment : Failure (Shows error) + } + AddComment --> Details : Escape (Cancel) + + state EditIssue { + [*] --> EditingIssueForm + EditingIssueForm --> SubmittingEdit : Ctrl+S (Submit) + SubmittingEdit --> ViewingDetails : Success (Details reload) + SubmittingEdit --> EditingIssueForm : Failure (Shows error) + } + EditIssue --> Details : Escape (Cancel) + + state AddTime { + [*] --> EnteringTime + EnteringTime --> SubmittingTime : Ctrl+S (Submit) + SubmittingTime --> ViewingDetails : Success (Time reloads) + SubmittingTime --> EnteringTime : Failure (Shows error) + } + AddTime --> Details : Escape (Cancel) + + state SetAssignees { + [*] --> EnteringAssignees + EnteringAssignees --> SubmittingAssignees : Enter (Submit) + SubmittingAssignees --> ViewingDetails : Success (Assignees reload) + SubmittingAssignees --> EnteringAssignees : Failure (Shows error) + } + SetAssignees --> Details : Escape (Cancel) + + state ConfirmStateChange { + [*] --> AwaitingConfirmation + AwaitingConfirmation --> Details : No / Escape (Cancel) + AwaitingConfirmation --> AnimatingChange : Yes ('y' key) + + state AnimatingChange { + [*] --> AnimatingClose : If Closing (Gravestones) + [*] --> AnimatingReopen : If Reopening (Zombies) + AnimatingClose --> ExecutingStateChangeAPI : Finish Frames (1.5s) + AnimatingReopen --> ExecutingStateChangeAPI : Finish Frames (1.8s) + } + ExecutingStateChangeAPI --> ViewingDetails : Done & Reloaded + } +``` + +--- + +## 3. Data Interaction & Rerender Lifecycle + +When the user interacts with the interface, keyboard events are processed by the active screen key handler, state variables are updated, API calls are made if necessary, and a full screen redraw is triggered on the terminal buffer. + +The following sequence diagram details the lifecyle of adding a comment to an issue: + +```mermaid +sequenceDiagram + autonumber + actor User as User Terminal + participant TUI as TuiEngine (tui.ts) + participant API as API Layer (api.ts) + participant Gitea as Gitea REST API + + User->>TUI: Keypress (e.g. 'c' on Details screen) + TUI->>TUI: Update screen state to 'add-comment' + TUI->>TUI: Render text input area (addCommentForm) + TUI-->>User: Display empty comment field + + User->>TUI: Type comment text + Ctrl+S + TUI->>TUI: Set state.loading = true & render spinner + TUI->>API: createIssueComment(config, issueNumber, commentBody) + API->>Gitea: POST /repos/{owner}/{repo}/issues/{index}/comments + Gitea-->>API: 201 Created (Comment entity JSON) + API-->>TUI: Comment Object + + TUI->>TUI: Set screen state to 'details' + TUI->>TUI: Trigger loadComments(issue) (Async) + TUI->>API: fetchIssueComments(config, issueNumber) + API->>Gitea: GET /repos/{owner}/{repo}/issues/{index}/comments + Gitea-->>API: 200 OK (Comments list JSON) + API-->>TUI: Comment[] Array + TUI->>TUI: Set state.loading = false + TUI->>TUI: Render details screen + updated comments + TUI-->>User: Display issue details and new comment +``` + +--- + +## 4. Module Directories & Code Structure + +The project code is organized into a clean multi-file architecture located under [src](file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/src): + +1. **[index.ts](file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/src/index.ts)**: + - Sets up Command-line parsing with the `commander` package. + - Automatically bootstraps the configuration and constructs the default `AppState`. + - Checks CLI credentials. If valid, validates them directly, bypassing the setup page and going straight to the issue dashboard. + - Instantiates the `TuiEngine` and starts the app loop. + +2. **[tui.ts](file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/src/tui.ts)**: + - Contains the core `TuiEngine` class which manages key listeners on `process.stdin` (in raw mode). + - Manages loading spinners and UI animations (Gravestones for closed issues and Zombies for reopened issues). + - Performs terminal buffer manipulation (e.g. entering alternate screen buffer `\x1B[?1049h`, hiding cursor `\x1B[?25l`). + - Delegates the rendering of specific screens using modular helper functions (e.g., `renderSetupScreen`, `renderListScreen`, `renderDetailsScreen`). + +3. **[api.ts](file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/src/api.ts)**: + - Hosts the REST API layer built on top of `axios`. + - Interacts with Forgejo/Gitea endpoints such as `/repos`, `/issues`, `/comments`, and `/times`. + - Handles network errors and formats them into user-friendly diagnostic messages. + +4. **[config.ts](file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/src/config.ts)**: + - Manages persistence of credentials. + - Reads configuration locally from `fjtui.json` and globally from `~/.config/fjtui/fjtui.json`. Local configurations take priority. + - Writes updated credentials back to the user's home configuration safely. + +5. **[types.ts](file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/src/types.ts)**: + - Declares the TypeScript contracts and interfaces representing the application state (`AppState`), target forms (`SetupForm`, `CreateIssueForm`, etc.), and returned entities (`User`, `Issue`, `Label`, `Comment`, `RepoItem`). + +6. **ASCII Art Assets**: + - [ascii-art.txt](file:///wsl.localhost/Ubuntu/home/builder/Workspaces/fjtui/ascii-art.txt): ASCII art loaded at startup to perform the color-shifting launch animation. diff --git a/ascii-art-2.txt b/ascii-art-2.txt new file mode 100755 index 0000000..8535356 --- /dev/null +++ b/ascii-art-2.txt @@ -0,0 +1,55 @@ + + + + + + + + + + + .. + -. + :+. + .:=+***#****++++-.*+++: .++ + .-+********#%%#**++++++%***- +*: + :+###%%%%@@@@@%%%%#%%%%%@@@@@@= .+*= + ..............:#@%%%%%%%@*:%@@@+ :**+. + #@%%%#@% .=***. + =@@%%%@+ .+****. + =%%@@+ .+*####+. + -##@@+ .-********- + -**@@+ .:-+**#********. + -**%#*********#******. + -**********#*******+ + =***********#*####*: + .+***********####*=. + =******#***++=:. + .+: -**@@+ + :. -**@@+ + -**@@+ + .......:::::....................... + .::::::::::::-+#*************####@@@@@@@@@@@@@@@@@@@@: + .%@%*********#%#######%%%%%%%%%%@@@@@@@@@@@@@%#*=-:. + .*@@@@%##**####%%%######@@@@@@@@@@@@@@@@* + .+@@@@@@%###%%#******@@@@@@@@%=.. ... + .-+%@@@%%#******@@@@@@@+ + ::-******@@@@@@= + .******@@@@@@. + :******@@@@@@= + -*****##@@@@@@@= + .-+**#@@@@@@@@%%#%%#=. + *%%%%%%@@@@@@@@@@%%#%@@@@@@# + :--+****************++++******+--: + + + + + + + + + + + + diff --git a/ascii-art.txt b/ascii-art.txt new file mode 100755 index 0000000..f2e60f7 --- /dev/null +++ b/ascii-art.txt @@ -0,0 +1,55 @@ + + + + + + + + + + + + . + = + .-==+*+===---: +--- += + -++++++++*##*++-----:#=== =+ + :+**##%%%@@@%####*%####@@%%%%: +*- + *%######%@+ %@@@- +*= + *%###*%# -**+ + .%%##%@: =***+ + .##@@- =*****= + .++@@- :********. + .++@@- .=+*********+ + .++%*+++++**********+ + .++++++++**********- + -++++++***********+ + =++++++++*******+: + -+++++**+++=-: + + ++@@- + .++@@- + .++@@- + + .-+++++++++++*******@@@@@@@@@@@@@@@@@@@% + #@#+++++++++*****#***#####%%%%%@@@@@@@@@@@@%#*=:. + =@@@@#**++****###******@@@@@@@@@@@@@@@@= + -@@@@@@#***##***++++@@@@@@@@%: + .-#@@%##*++++++@@@@@@@: + .++++++@@@@@@. + ++++++@@@@@@ + ++++++@@@@@@: + .++++***@@@@@@@: + .=++*%@@@@@@@%#*##*: + +%%%%%%@@@@@@@@@@%#+%%%%%%%* + -=================---======- + + + + + + + + + + + + diff --git a/src/tui.ts b/src/tui.ts index fb879ce..4d7a8f5 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -1,5 +1,8 @@ import readline from 'readline'; import chalk from 'chalk'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; import { AppState, Issue, Comment } from './types.js'; import { fetchIssues, fetchIssue, fetchIssueComments, validateConnection, normalizeUrl, authenticateAndFetchRepos, createIssue, createIssueComment, editIssue, addIssueTime, changeIssueState, setIssueAssignees } from './api.js'; import { saveGlobalConfig } from './config.js'; @@ -191,6 +194,11 @@ export class TuiEngine { private searchInputBuffer: string = ''; private animationFrame: number = 0; private animationInterval: NodeJS.Timeout | null = null; + private launchDestScreen: 'list' | 'setup' = 'setup'; + private launchFrame: string[] = []; + private launchFrameIndex = 0; + private launchInterval: NodeJS.Timeout | null = null; + private maxLaunchFrames = 60; constructor(initialState: AppState) { this.state = initialState; @@ -217,13 +225,15 @@ export class TuiEngine { // Enter alternate screen buffer and hide standard cursor process.stdout.write('\x1B[?1049h\x1B[?25l'); - // Bootstrap if config exists + // Bootstrap destination if (this.state.config.url && this.state.config.owner && this.state.config.repo) { - this.state.screen = 'list'; - this.loadIssues(); + this.launchDestScreen = 'list'; } else { - this.render(); + this.launchDestScreen = 'setup'; } + + this.state.screen = 'launch'; + this.startLaunchAnimation(); } /** @@ -235,6 +245,10 @@ export class TuiEngine { clearInterval(this.animationInterval); this.animationInterval = null; } + if (this.launchInterval) { + clearInterval(this.launchInterval); + this.launchInterval = null; + } // Exit alternate screen buffer and show standard cursor process.stdout.write('\x1B[?1049l\x1B[?25h'); if (process.stdin.isTTY) { @@ -361,6 +375,11 @@ export class TuiEngine { return; } + if (this.state.screen === 'launch') { + this.skipLaunchAnimation(); + return; + } + if (this.state.screen === 'setup') { this.handleSetupKeypress(str, key); } else if (this.state.screen === 'repo-picker') { @@ -878,7 +897,9 @@ export class TuiEngine { // Clear terminal screen, clear scrollback buffer, and reset cursor position process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); - if (this.state.screen === 'setup') { + if (this.state.screen === 'launch') { + this.renderLaunchScreen(cols, rows); + } else if (this.state.screen === 'setup') { this.renderSetupScreen(cols, rows); } else if (this.state.screen === 'repo-picker') { this.renderRepoPickerScreen(cols, rows); @@ -2204,4 +2225,178 @@ export class TuiEngine { this.render(); } } + + // LAUNCH ANIMATION IMPLEMENTATION + private startLaunchAnimation() { + this.launchFrameIndex = 0; + this.render(); + + this.launchInterval = setInterval(() => { + this.launchFrameIndex++; + this.render(); + + if (this.launchFrameIndex >= this.maxLaunchFrames - 1) { + this.skipLaunchAnimation(); + } + }, 60); // 60ms interval for fast, smooth fluid color shifting + } + + private skipLaunchAnimation() { + if (this.launchInterval) { + clearInterval(this.launchInterval); + this.launchInterval = null; + } + + // Transition to target destination screen + if (this.launchDestScreen === 'list') { + this.state.screen = 'list'; + this.loadIssues(); + } else { + this.state.screen = 'setup'; + this.render(); + } + } + + private loadLaunchFrames() { + try { + const __filename = fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + // Resolve path to the root folder where ascii-art.txt is located + const artPath = path.resolve(__dirname, '..', 'ascii-art.txt'); + + if (fs.existsSync(artPath)) { + const rawArt = fs.readFileSync(artPath, 'utf8').split(/\r?\n/); + this.launchFrame = this.preprocessArt(rawArt); + } else { + // Fallback frames if file not found + this.launchFrame = [ + " ( ( ", + " ) ) ", + " [===] ", + " \\___/ " + ]; + } + } catch (e) { + this.launchFrame = [ + " ( ( ", + " ) ) ", + " [===] ", + " \\___/ " + ]; + } + } + + private preprocessArt(lines: string[]): string[] { + let startIdx = 0; + while (startIdx < lines.length && lines[startIdx].trim() === '') { + startIdx++; + } + let endIdx = lines.length - 1; + while (endIdx >= 0 && lines[endIdx].trim() === '') { + endIdx--; + } + + if (startIdx > endIdx) return []; + const activeLines = lines.slice(startIdx, endIdx + 1); + + let minPadding = Infinity; + for (const line of activeLines) { + if (line.trim() === '') continue; + const match = line.match(/^[ \t]*/); + const padding = match ? match[0].length : 0; + if (padding < minPadding) { + minPadding = padding; + } + } + + if (minPadding === Infinity || minPadding === 0) return activeLines; + return activeLines.map(line => line.length >= minPadding ? line.substring(minPadding) : ''); + } + + private renderLaunchScreen(cols: number, rows: number) { + if (this.launchFrame.length === 0) { + this.loadLaunchFrames(); + } + + const frame = this.launchFrame; + if (frame.length === 0) return; + + const W = Math.max(...frame.map(line => line.length)); + const H = frame.length; + + const padLeftCount = Math.max(0, Math.floor((cols - W) / 2)); + const padTopCount = Math.max(0, Math.floor((rows - H - 5) / 2)); + + for (let i = 0; i < padTopCount; i++) { + console.log(''); + } + + // HSL helper function to generate HSL-based hex colors + const hslToHex = (h: number, s: number, l: number): string => { + l /= 100; + const a = (s * Math.min(l, 1 - l)) / 100; + const f = (n: number) => { + const k = (n + h / 30) % 12; + const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); + return Math.round(255 * color).toString(16).padStart(2, '0'); + }; + return `#${f(0)}${f(8)}${f(4)}`; + }; + + const step = this.launchFrameIndex; + + for (let i = 0; i < frame.length; i++) { + const line = frame[i]; + const leftPad = ' '.repeat(padLeftCount); + + let coloredLine = ''; + if (i < 20) { + // Steam gradient: waving light intensity using sine wave based on time and row index + const factor = i / 20; // 0 to 1 + + // Wave lightness of the steam + const wave = Math.sin(step * 0.4 - i * 0.3); + const lightness = Math.max(10, Math.min(80, Math.round(25 + wave * 15 + factor * 25))); + + // Soft cyan/blue steam hue = 195 + const hex = hslToHex(195, 30, lightness); + coloredLine = chalk.hex(hex)(line); + } else { + // Cup body gradient: shifting rainbow wave + const rowOffset = i - 20; + + // Cycle the hue over time and rows + const hue = (step * 8 + rowOffset * 12) % 360; + const hex = hslToHex(hue, 95, 55); + coloredLine = chalk.hex(hex).bold(line); + } + + console.log(leftPad + coloredLine); + } + + console.log(''); + const titlePlain = ' G I T E A & F O R G E J O T U I '; + const subtitlePlain = 'Interactive Issue & Pull Request Explorer'; + + // Cycle the title box background color too! + const titleHue = (step * 6) % 360; + const titleColor = hslToHex(titleHue, 95, 50); + + const titleLeftPad = Math.max(0, Math.floor((cols - titlePlain.length) / 2)); + const subtitleLeftPad = Math.max(0, Math.floor((cols - subtitlePlain.length) / 2)); + + console.log(' '.repeat(titleLeftPad) + chalk.bold.bgHex(titleColor).black(titlePlain)); + console.log(' '.repeat(subtitleLeftPad) + chalk.gray(subtitlePlain)); + console.log(''); + + const skipPlain = 'Press any key to skip animation'; + const skipLeftPad = Math.max(0, Math.floor((cols - skipPlain.length) / 2)); + console.log(' '.repeat(skipLeftPad) + chalk.gray.italic(skipPlain)); + + const printedRows = H + padTopCount + 5; + const remainingRows = Math.max(0, rows - printedRows - 1); + for (let i = 0; i < remainingRows; i++) { + console.log(''); + } + } } diff --git a/src/types.ts b/src/types.ts index c3c1b3f..ebb43d8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -70,7 +70,7 @@ export interface AddTimeForm { timeInput: string; } -export type ScreenType = 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time' | 'confirm-state-change' | 'animating-close' | 'animating-reopen' | 'set-assignees'; +export type ScreenType = 'launch' | 'setup' | 'repo-picker' | 'list' | 'details' | 'create-issue' | 'add-comment' | 'edit-issue' | 'add-time' | 'confirm-state-change' | 'animating-close' | 'animating-reopen' | 'set-assignees'; export interface RepoItem {