Merge pull request #19 from phranck/rename/tuikit-to-tuikit

Chore: Rename TUIKit to TUIkit
This commit is contained in:
phranck
2026-01-30 20:37:11 +01:00
94 changed files with 221 additions and 222 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

+2 -2
View File
@@ -77,7 +77,7 @@ jobs:
run: |
swift package --allow-writing-to-directory docs-output \
generate-documentation \
--target TUIKit \
--target TUIkit \
--output-path docs-output \
--transform-for-static-hosting
@@ -117,7 +117,7 @@ jobs:
<meta http-equiv="refresh" content="0; url=/documentation/tuikit" />
</head>
<body>
<a href="/documentation/tuikit">Redirecting to TUIKit Documentation...</a>
<a href="/documentation/tuikit">Redirecting to TUIkit Documentation...</a>
</body>
</html>
HTMLEOF
+1 -1
View File
@@ -1,4 +1,4 @@
# SwiftLint Configuration for TUIKit
# SwiftLint Configuration for TUIkit
# Only lint project sources (not build artifacts or dependencies)
included:
+11 -11
View File
@@ -17,23 +17,23 @@
- **Aspect Ratio**: 16:9 (landscape)
- **Recommended Size**: 1000×560px or larger (up to 2000px width for retina)
- **Format**: PNG, JPG, or WebP (lossless recommended)
- **Content**: Screenshot/demo of TUIKit in action
- **Content**: Screenshot/demo of TUIkit in action
- Terminal UI showing an interactive application
- Nice visualization of TUIKit components
- Nice visualization of TUIkit components
- Colors: Green (#10b981), dark background (#030712)
- Examples: Menu selection, modal dialogs, text input, etc.
**HTML Location**: `docs/index.html` - Line ~555-591
```html
<div class="hero-video">
<!-- Replace this SVG with: <img src="images/hero-demo.png" alt="TUIKit Demo"> -->
<!-- Replace this SVG with: <img src="images/hero-demo.png" alt="TUIkit Demo"> -->
<svg>...</svg>
</div>
```
**Placeholder Preview**:
- Terminal window with green border
- Shows TUIKit menu example
- Shows TUIkit menu example
- Command: `$ swift run MyApp`
- Interactive menu selection
@@ -48,8 +48,8 @@
- **Aspect Ratio**: 16:9 or Square (1:1)
- **Recommended Size**: 600×400px (or 600×600px)
- **Format**: PNG, JPG, or WebP
- **Content**: Professional screenshot of TUIKit features
- Shows real TUIKit app with actual components
- **Content**: Professional screenshot of TUIkit features
- Shows real TUIkit app with actual components
- Terminal UI showcase with multiple features
- Examples of: Menu, Button, Text, Panel, Dialog, etc.
- Color scheme: Green theme with different appearance styles
@@ -59,13 +59,13 @@
**HTML Location**: `docs/index.html` - Line ~641-685
```html
<div class="advanced-image">
<!-- Replace this SVG with: <img src="images/advanced-demo.png" alt="Advanced TUIKit Features"> -->
<!-- Replace this SVG with: <img src="images/advanced-demo.png" alt="Advanced TUIkit Features"> -->
<svg>...</svg>
</div>
```
**Placeholder Preview**:
- Advanced TUIKit Demo terminal window
- Advanced TUIkit Demo terminal window
- Two feature boxes: "Theme: Green Phosphor" + "Components"
- Live example: Interactive menu with selection
@@ -157,7 +157,7 @@ Find this in `docs/index.html` around line 554:
Replace with:
```html
<div class="hero-video">
<img src="images/hero-demo.png" alt="TUIKit Demo Application" style="width: 100%; height: 100%; object-fit: cover; border-radius: 1rem;">
<img src="images/hero-demo.png" alt="TUIkit Demo Application" style="width: 100%; height: 100%; object-fit: cover; border-radius: 1rem;">
</div>
```
@@ -172,7 +172,7 @@ Find this in `docs/index.html` around line 641:
Replace with:
```html
<div class="advanced-image">
<img src="images/advanced-demo.png" alt="Advanced TUIKit Features" style="width: 100%; height: 100%; object-fit: cover; border-radius: 1rem;">
<img src="images/advanced-demo.png" alt="Advanced TUIkit Features" style="width: 100%; height: 100%; object-fit: cover; border-radius: 1rem;">
</div>
```
@@ -193,7 +193,7 @@ With:
All current images are high-quality SVG placeholders that:
- ✅ Match the dark theme and color scheme
- ✅ Show relevant TUIKit UI examples
- ✅ Show relevant TUIkit UI examples
- ✅ Are vector-based (scale perfectly)
- ✅ Look professional on their own
- ✅ Are easy to replace
+10 -10
View File
@@ -14,7 +14,7 @@ let swiftLintPlugin: [Target.PluginUsage] = enableSwiftLint
: []
let package = Package(
name: "TUIKit",
name: "TUIkit",
// Minimum deployment targets for Apple platforms
// Linux is automatically supported (no platform specification needed)
platforms: [
@@ -22,12 +22,12 @@ let package = Package(
],
products: [
.library(
name: "TUIKit",
targets: ["TUIKit"]
name: "TUIkit",
targets: ["TUIkit"]
),
.executable(
name: "TUIKitExample",
targets: ["TUIKitExample"]
name: "TUIkitExample",
targets: ["TUIkitExample"]
),
],
dependencies: [
@@ -36,17 +36,17 @@ let package = Package(
],
targets: [
.target(
name: "TUIKit",
name: "TUIkit",
plugins: swiftLintPlugin
),
.executableTarget(
name: "TUIKitExample",
dependencies: ["TUIKit"],
name: "TUIkitExample",
dependencies: ["TUIkit"],
plugins: swiftLintPlugin
),
.testTarget(
name: "TUIKitTests",
dependencies: ["TUIKit"]
name: "TUIkitTests",
dependencies: ["TUIkit"]
),
]
)
+15 -16
View File
@@ -1,22 +1,21 @@
[![CI](https://github.com/phranck/TUIKit/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/phranck/TUIKit/actions/workflows/ci.yml)
[![CI](https://github.com/phranck/TUIkit/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/phranck/TUIkit/actions/workflows/ci.yml)
![Tests](https://img.shields.io/badge/Tests-181_passing-brightgreen)
![Swift 6.0](https://img.shields.io/badge/Swift-6.0-F05138?logo=swift&logoColor=white)
![macOS](https://img.shields.io/badge/Platform-macOS-000000?logo=apple&logoColor=white)
![Linux](https://img.shields.io/badge/Platform-Linux-FCC624?logo=linux&logoColor=black)
![Platforms](https://img.shields.io/badge/Platforms-macOS%20%7C%20Linux-blue)
![License](https://img.shields.io/badge/License-CC--BY--NC--SA%204.0-lightgrey?style=flat)
![Spotnik - Terminal based Sptoify Player](.github/assets/spotnik_1.png)
![TUIkit Banner](.github/assets/github-banner.png)
# TUIKit
# TUIkit
A SwiftUI-like framework for building Terminal User Interfaces in Swift — no ncurses, no C dependencies, just pure Swift.
## What is this?
TUIKit lets you build TUI apps using the same declarative syntax you already know from SwiftUI. Define your UI with `View`, compose views with `VStack`, `HStack`, and `ZStack`, style text with modifiers like `.bold()` and `.foregroundColor(.red)`, and run it all in your terminal.
TUIkit lets you build TUI apps using the same declarative syntax you already know from SwiftUI. Define your UI with `View`, compose views with `VStack`, `HStack`, and `ZStack`, style text with modifiers like `.bold()` and `.foregroundColor(.red)`, and run it all in your terminal.
```swift
import TUIKit
import TUIkit
@main
struct MyApp: App {
@@ -32,7 +31,7 @@ struct ContentView: View {
var body: some View {
VStack(spacing: 1) {
Text("Hello, TUIKit!")
Text("Hello, TUIkit!")
.bold()
.foregroundColor(.cyan)
@@ -85,18 +84,18 @@ struct ContentView: View {
## Run the Example App
```bash
swift run TUIKitExample
swift run TUIkitExample
```
Press `q` or `ESC` to exit.
## Installation
Add TUIKit to your `Package.swift`:
Add TUIkit to your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/phranck/TUIKit.git", branch: "main")
.package(url: "https://github.com/phranck/TUIkit.git", branch: "main")
]
```
@@ -105,13 +104,13 @@ Then add it to your target:
```swift
.target(
name: "YourApp",
dependencies: ["TUIKit"]
dependencies: ["TUIkit"]
)
```
## Theming
TUIKit includes 8 predefined themes inspired by classic terminals:
TUIkit includes 8 predefined themes inspired by classic terminals:
```swift
@main
@@ -146,16 +145,16 @@ Available themes:
```
Sources/
├── TUIKit/
├── TUIkit/
│ ├── App/ App, Scene, WindowGroup
│ ├── Core/ View, ViewBuilder, State, Environment, Color, Theme
│ ├── Modifiers/ Border, Frame, Padding, Overlay, Lifecycle
│ ├── Rendering/ Terminal, ANSIRenderer, ViewRenderer, FrameBuffer
│ └── Views/ Text, Stacks, Button, Menu, Alert, StatusBar, ...
└── TUIKitExample/ Example app (executable target)
└── TUIkitExample/ Example app (executable target)
Tests/
└── TUIKitTests/ 181 tests across 27 test suites
└── TUIkitTests/ 181 tests across 27 test suites
```
## Requirements
@@ -1,17 +1,17 @@
//
// App.swift
// TUIKit
// TUIkit
//
// The base protocol for TUIKit applications.
// The base protocol for TUIkit applications.
//
import Foundation
// MARK: - App Protocol
/// The base protocol for TUIKit applications.
/// The base protocol for TUIkit applications.
///
/// `App` is the entry point for every TUIKit application,
/// `App` is the entry point for every TUIkit application,
/// similar to `App` in SwiftUI.
///
/// # Example
@@ -1,6 +1,6 @@
//
// InputHandler.swift
// TUIKit
// TUIkit
//
// Dispatches key events through a 3-layer priority chain.
//
@@ -1,6 +1,6 @@
//
// RenderLoop.swift
// TUIKit
// TUIkit
//
// Manages the rendering pipeline: scene rendering, environment
// assembly, and status bar output.
@@ -1,11 +1,11 @@
//
// Scene.swift
// TUIKit
// TUIkit
//
// Scene types for TUIKit applications.
// Scene types for TUIkit applications.
//
/// The base protocol for scenes in TUIKit.
/// The base protocol for scenes in TUIkit.
///
/// A scene represents a part of the app structure,
/// typically a window or a group of views.
@@ -1,6 +1,6 @@
//
// SignalManager.swift
// TUIKit
// TUIkit
//
// Manages POSIX signal handlers for terminal resize and graceful shutdown.
//
@@ -1,6 +1,6 @@
//
// StatusBarState.swift
// TUIKit
// TUIkit
//
// Manages the status bar state for the running application.
//
@@ -1,6 +1,6 @@
//
// AppStorage.swift
// TUIKit
// TUIkit
//
// Persistent storage for app settings using @AppStorage property wrapper.
//
@@ -51,7 +51,7 @@ private func appConfigDirectory() -> URL {
/// A storage backend that persists data to a JSON file.
///
/// This is the default storage backend for TUIKit apps.
/// This is the default storage backend for TUIkit apps.
/// Data is stored in `$XDG_CONFIG_HOME/[appName]/settings.json`
/// or `~/.config/[appName]/settings.json` as fallback.
public final class JSONFileStorage: StorageBackend, @unchecked Sendable {
@@ -1,6 +1,6 @@
//
// Appearance.swift
// TUIKit
// TUIkit
//
// Appearance system for consistent UI control styling.
//
@@ -1,6 +1,6 @@
//
// BorderStyle.swift
// TUIKit
// TUIkit
//
// Border styles and character sets for TUI borders.
//
@@ -1,11 +1,11 @@
//
// Color.swift
// TUIKit
// TUIkit
//
// Color definitions for terminal output with ANSI escape codes.
//
/// A color for use in TUIKit views.
/// A color for use in TUIkit views.
///
/// `Color` represents standard ANSI colors as well as
/// extended 256-color palette and True Color (24-bit RGB).
@@ -1,6 +1,6 @@
//
// Environment.swift
// TUIKit
// TUIkit
//
// Environment system for passing values down the view hierarchy.
// Similar to SwiftUI's @Environment property wrapper.
@@ -209,7 +209,7 @@ extension EnvironmentModifier: Renderable {
// Render content with modified environment
return EnvironmentStorage.shared.withEnvironment(modifiedEnvironment) {
TUIKit.renderToBuffer(content, context: modifiedContext)
TUIkit.renderToBuffer(content, context: modifiedContext)
}
}
}
@@ -1,6 +1,6 @@
//
// Focus.swift
// TUIKit
// TUIkit
//
// Focus management system for interactive views.
//
@@ -1,8 +1,8 @@
//
// KeyEvent.swift
// TUIKit
// TUIkit
//
// Keyboard event handling for TUIKit.
// Keyboard event handling for TUIkit.
//
import Foundation
@@ -1,6 +1,6 @@
//
// Preferences.swift
// TUIKit
// TUIkit
//
// Preferences system for bottom-up data flow (child → parent).
// Similar to SwiftUI's PreferenceKey system.
@@ -214,7 +214,7 @@ extension PreferenceModifier: Renderable {
context.tuiContext.preferences.setValue(value, forKey: K.self)
// Render content
return TUIKit.renderToBuffer(content, context: context)
return TUIkit.renderToBuffer(content, context: context)
}
}
@@ -245,7 +245,7 @@ extension OnPreferenceChangeModifier: Renderable {
prefs.push()
// Render content
let buffer = TUIKit.renderToBuffer(content, context: context)
let buffer = TUIkit.renderToBuffer(content, context: context)
// Pop and get collected preferences
let preferences = prefs.pop()
@@ -1,6 +1,6 @@
//
// PrimitiveViews.swift
// TUIKit
// TUIkit
//
// Primitive view types that serve as leaves in the view tree.
//
@@ -1,8 +1,8 @@
//
// State.swift
// TUIKit
// TUIkit
//
// State management for TUIKit views.
// State management for TUIkit views.
//
import Foundation
@@ -11,7 +11,7 @@ import Foundation
/// Global application state that triggers re-renders when modified.
///
/// Since TUIKit runs in a single-threaded event loop, we use a simple
/// Since TUIkit runs in a single-threaded event loop, we use a simple
/// observable pattern. The AppRunner subscribes to state changes and
/// re-renders when notified.
public final class AppState: @unchecked Sendable {
@@ -1,6 +1,6 @@
//
// TUIContext.swift
// TUIKit
// TUIkit
//
// Central dependency container replacing scattered singletons.
// Owned by AppRunner and threaded through RenderContext.
@@ -174,7 +174,7 @@ public final class LifecycleManager: @unchecked Sendable {
// MARK: - TUI Context
/// Central dependency container for TUIKit runtime services.
/// Central dependency container for TUIkit runtime services.
///
/// `TUIContext` replaces the scattered singleton pattern by bundling all
/// framework-internal services into a single object owned by `AppRunner`.
@@ -197,7 +197,7 @@ public final class LifecycleManager: @unchecked Sendable {
/// context.tuiContext.keyEventDispatcher.addHandler { event in
/// // handle key
/// }
/// return TUIKit.renderToBuffer(content, context: context)
/// return TUIkit.renderToBuffer(content, context: context)
/// }
/// }
/// ```
@@ -1,6 +1,6 @@
//
// Theme.swift
// TUIKit
// TUIkit
//
// Palette system with full 16M color support and predefined terminal palettes.
//
@@ -9,10 +9,10 @@ import Foundation
// MARK: - Palette Protocol
/// A palette defines the color scheme for a TUIKit application.
/// A palette defines the color scheme for a TUIkit application.
///
/// Palettes provide semantic colors that views use for consistent styling.
/// TUIKit includes several predefined palettes inspired by classic terminals.
/// TUIkit includes several predefined palettes inspired by classic terminals.
///
/// Conforms to ``Cyclable`` so it can be managed by a ``ThemeManager``.
///
@@ -1,6 +1,6 @@
//
// ThemeManager.swift
// TUIKit
// TUIkit
//
// Generic manager for cycling through items that conform to `Cyclable`.
// Replaces the previously duplicated palette manager and appearance manager
@@ -43,7 +43,7 @@ public protocol Cyclable: Sendable {
/// set a specific item by reference, and apply the current selection to the
/// environment so all views pick up the change.
///
/// TUIKit uses two instances:
/// TUIkit uses two instances:
/// - A ``PaletteManager`` for color palettes
/// - An ``AppearanceManager`` for border-style appearances
///
@@ -1,6 +1,6 @@
//
// TupleViews.swift
// TUIKit
// TUIkit
//
// Container type for multiple views in ViewBuilder, using Swift Parameter Packs.
//
@@ -1,6 +1,6 @@
//
// UserDefaultsStorage.swift
// TUIKit
// TUIkit
//
// UserDefaults-compatible storage backend for all platforms.
//
@@ -1,13 +1,13 @@
//
// View.swift
// TUIKit
// TUIkit
//
// The base protocol for all TUIKit views.
// The base protocol for all TUIkit views.
//
/// The base protocol for all TUIKit views.
/// The base protocol for all TUIkit views.
///
/// `View` is the central protocol in TUIKit and works similarly to `View` in SwiftUI.
/// `View` is the central protocol in TUIkit and works similarly to `View` in SwiftUI.
/// It defines how components declare their structure and content.
///
/// Every View defines a `body` composed of other Views.
@@ -15,7 +15,7 @@
///
/// ## Dual Rendering System
///
/// TUIKit uses two rendering paths:
/// TUIkit uses two rendering paths:
///
/// - **Composite views** implement `body` to compose other views.
/// The rendering system recurses into `body` to resolve the tree.
@@ -31,7 +31,7 @@
/// ```swift
/// struct MyView: View {
/// var body: some View {
/// Text("Hello, TUIKit!")
/// Text("Hello, TUIkit!")
/// }
/// }
/// ```
@@ -1,6 +1,6 @@
//
// ViewBuilder.swift
// TUIKit
// TUIkit
//
// Result builder for declarative view composition.
//
@@ -1,6 +1,6 @@
//
// ViewModifier.swift
// TUIKit
// TUIkit
//
// The view modifier system for transforming views.
//
@@ -60,7 +60,7 @@ public struct ModifiedView<Content: View, Modifier: ViewModifier>: View {
extension ModifiedView: Renderable {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
let childBuffer = TUIKit.renderToBuffer(content, context: context)
let childBuffer = TUIkit.renderToBuffer(content, context: context)
return modifier.modify(buffer: childBuffer, context: context)
}
}
@@ -1,6 +1,6 @@
//
// PrimitiveTypes+Extensions.swift
// TUIKit
// TUIkit
//
// Protocol conformances for standard types (Never, Optional).
//
@@ -38,7 +38,7 @@ extension Optional: Renderable where Wrapped: View {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
switch self {
case .some(let view):
return TUIKit.renderToBuffer(view, context: context)
return TUIkit.renderToBuffer(view, context: context)
case .none:
return FrameBuffer()
}
@@ -1,6 +1,6 @@
//
// String+ANSI.swift
// TUIKit
// TUIkit
//
// ANSI-aware String helpers for visible-length calculation and padding.
//
@@ -1,6 +1,6 @@
//
// View+Convenience.swift
// TUIKit
// TUIkit
//
// Convenience view extensions: modal, asAnyView.
//
@@ -1,6 +1,6 @@
//
// View+Environment.swift
// TUIKit
// TUIkit
//
// Environment injection view modifiers: environment, appearance, theme.
//
@@ -1,6 +1,6 @@
//
// View+Events.swift
// TUIKit
// TUIkit
//
// Event handling and lifecycle view modifiers: onKeyPress, onAppear, onDisappear, task, statusBarItems.
//
@@ -1,6 +1,6 @@
//
// View+Layout.swift
// TUIKit
// TUIkit
//
// Layout and styling view modifiers: border, dimmed, background, frame, overlay, padding.
//
@@ -1,6 +1,6 @@
//
// View+Modifier.swift
// TUIKit
// TUIkit
//
// Generic modifier application extension for View.
//
@@ -1,6 +1,6 @@
//
// View+Preferences.swift
// TUIKit
// TUIkit
//
// Preference system view modifiers: preference, onPreferenceChange, navigationTitle.
//
@@ -1,6 +1,6 @@
//
// BackgroundModifier.swift
// TUIKit
// TUIkit
//
// The .background() modifier for adding background colors to views.
//
@@ -1,6 +1,6 @@
//
// BorderModifier.swift
// TUIKit
// TUIkit
//
// The .border() modifier for adding borders around views.
//
@@ -37,7 +37,7 @@ extension BorderedView: Renderable {
contentContext.availableWidth = max(1, context.availableWidth - BorderRenderer.borderWidthOverhead)
// Render content with reduced width
let buffer = TUIKit.renderToBuffer(content, context: contentContext)
let buffer = TUIkit.renderToBuffer(content, context: contentContext)
guard !buffer.isEmpty else { return buffer }
@@ -1,6 +1,6 @@
//
// DimmedModifier.swift
// TUIKit
// TUIkit
//
// A modifier that applies a dimming effect to the entire view content.
//
@@ -22,7 +22,7 @@ public struct DimmedModifier<Content: View>: View {
extension DimmedModifier: Renderable {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
let contentBuffer = TUIKit.renderToBuffer(content, context: context)
let contentBuffer = TUIkit.renderToBuffer(content, context: context)
guard !contentBuffer.isEmpty else {
return contentBuffer
@@ -1,6 +1,6 @@
//
// FrameModifier.swift
// TUIKit
// TUIkit
//
// The .frame() modifier for setting explicit size constraints.
//
@@ -81,7 +81,7 @@ extension FlexibleFrameView: Renderable {
}
// Render content
let buffer = TUIKit.renderToBuffer(content, context: contentContext)
let buffer = TUIkit.renderToBuffer(content, context: contentContext)
// Apply minimum constraints
var finalWidth = buffer.width
@@ -1,6 +1,6 @@
//
// KeyPressModifier.swift
// TUIKit
// TUIkit
//
// A modifier for handling keyboard events.
//
@@ -43,6 +43,6 @@ extension KeyPressModifier: Renderable {
}
// Render the content
return TUIKit.renderToBuffer(content, context: context)
return TUIkit.renderToBuffer(content, context: context)
}
}
@@ -1,6 +1,6 @@
//
// LifecycleModifier.swift
// TUIKit
// TUIkit
//
// Lifecycle modifiers: .onAppear(), .onDisappear(), .task()
//
@@ -117,7 +117,7 @@ extension OnAppearModifier: Renderable {
_ = context.tuiContext.lifecycle.recordAppear(token: token, action: action)
// Render content
return TUIKit.renderToBuffer(content, context: context)
return TUIkit.renderToBuffer(content, context: context)
}
}
@@ -185,7 +185,7 @@ extension OnDisappearModifier: Renderable {
_ = context.tuiContext.lifecycle.recordAppear(token: token, action: {})
// Render content
return TUIKit.renderToBuffer(content, context: context)
return TUIkit.renderToBuffer(content, context: context)
}
}
@@ -274,6 +274,6 @@ extension TaskModifier: Renderable {
}
// Render content
return TUIKit.renderToBuffer(content, context: context)
return TUIkit.renderToBuffer(content, context: context)
}
}
@@ -1,6 +1,6 @@
//
// OverlayModifier.swift
// TUIKit
// TUIkit
//
// A modifier that renders an overlay on top of the base view.
//
@@ -30,8 +30,8 @@ public struct OverlayModifier<Base: View, Overlay: View>: View {
extension OverlayModifier: Renderable {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
// Render both contents
let baseBuffer = TUIKit.renderToBuffer(base, context: context)
let overlayBuffer = TUIKit.renderToBuffer(overlay, context: context)
let baseBuffer = TUIkit.renderToBuffer(base, context: context)
let overlayBuffer = TUIkit.renderToBuffer(overlay, context: context)
guard !baseBuffer.isEmpty else {
return overlayBuffer
@@ -1,6 +1,6 @@
//
// PaddingModifier.swift
// TUIKit
// TUIkit
//
// The .padding() modifier for adding space around a view.
//
@@ -1,6 +1,6 @@
//
// StatusBarItemsModifier.swift
// TUIKit
// TUIkit
//
// A modifier that sets status bar items for a view.
//
@@ -64,6 +64,6 @@ extension StatusBarItemsModifier: Renderable {
}
// Render the content
return TUIKit.renderToBuffer(content, context: renderContext)
return TUIkit.renderToBuffer(content, context: renderContext)
}
}
@@ -1,6 +1,6 @@
//
// ANSIRenderer.swift
// TUIKit
// TUIkit
//
// ANSI escape code generation for terminal output.
//
@@ -1,6 +1,6 @@
//
// BorderRenderer.swift
// TUIKit
// TUIkit
//
// Centralized border rendering utilities for standard and block styles.
//
@@ -1,6 +1,6 @@
//
// FrameBuffer.swift
// TUIKit
// TUIkit
//
// A 2D text buffer for off-screen rendering before terminal output.
//
@@ -1,13 +1,13 @@
//
// Renderable.swift
// TUIKit
// TUIkit
//
// Protocol for views that can render themselves directly.
//
/// A protocol for views that produce terminal output directly.
///
/// TUIKit uses a **dual rendering system** inspired by SwiftUI:
/// TUIkit uses a **dual rendering system** inspired by SwiftUI:
///
/// - **`View.body`** — Compositional path: views declare *what* they
/// are made of by composing other `View` types.
@@ -123,7 +123,7 @@ public struct RenderContext {
/// Renders any `View` into a ``FrameBuffer`` using the dual rendering system.
///
/// This is the **single entry point** for all view rendering in TUIKit.
/// This is the **single entry point** for all view rendering in TUIkit.
/// Every recursive call in the view tree passes through this function.
///
/// ## Decision order
@@ -1,6 +1,6 @@
//
// Terminal.swift
// TUIKit
// TUIkit
//
// Terminal abstraction for input and output.
//
@@ -1,6 +1,6 @@
//
// ViewRenderer.swift
// TUIKit
// TUIkit
//
// Renders Views to terminal output via FrameBuffer.
//
@@ -264,9 +264,9 @@ extension ConditionalView: Renderable {
public func renderToBuffer(context: RenderContext) -> FrameBuffer {
switch self {
case .trueContent(let content):
return TUIKit.renderToBuffer(content, context: context)
return TUIkit.renderToBuffer(content, context: context)
case .falseContent(let content):
return TUIKit.renderToBuffer(content, context: context)
return TUIkit.renderToBuffer(content, context: context)
}
}
}
@@ -1,10 +1,10 @@
# Architecture
Understand the layer model and rendering pipeline of TUIKit.
Understand the layer model and rendering pipeline of TUIkit.
## Overview
TUIKit is structured in five layers, each building on the one below. This clean separation makes the framework easy to extend and maintain.
TUIkit is structured in five layers, each building on the one below. This clean separation makes the framework easy to extend and maintain.
## Layer Model
@@ -55,7 +55,7 @@ The rendering pipeline converts the view tree into terminal output:
## Event Loop
TUIKit runs a synchronous event loop:
TUIkit runs a synchronous event loop:
```
┌─────────────────────────┐
@@ -1,14 +1,14 @@
# Getting Started
Build your first terminal application with TUIKit.
Build your first terminal application with TUIkit.
## Overview
TUIKit is a Swift package that lets you create terminal user interfaces with a declarative, SwiftUI-like syntax. This guide walks you through setting up a project and building a simple app.
TUIkit is a Swift package that lets you create terminal user interfaces with a declarative, SwiftUI-like syntax. This guide walks you through setting up a project and building a simple app.
## Adding TUIKit to Your Project
## Adding TUIkit to Your Project
Add TUIKit as a dependency in your `Package.swift`:
Add TUIkit as a dependency in your `Package.swift`:
```swift
// swift-tools-version: 6.0
@@ -18,12 +18,12 @@ let package = Package(
name: "MyTUIApp",
platforms: [.macOS(.v10_15)],
dependencies: [
.package(url: "https://github.com/phranck/TUIKit.git", from: "0.1.0"),
.package(url: "https://github.com/phranck/TUIkit.git", from: "0.1.0"),
],
targets: [
.executableTarget(
name: "MyTUIApp",
dependencies: ["TUIKit"]
dependencies: ["TUIkit"]
),
]
)
@@ -34,7 +34,7 @@ let package = Package(
Create a `main.swift` file with the ``App`` protocol as your entry point:
```swift
import TUIKit
import TUIkit
@main
struct MyApp: App {
@@ -48,7 +48,7 @@ struct MyApp: App {
struct ContentView: View {
var body: some View {
VStack {
Text("Welcome to TUIKit!")
Text("Welcome to TUIkit!")
.bold()
.foregroundColor(.cyan)
@@ -86,11 +86,11 @@ struct CounterView: View {
For simple scripts that don't need a full app lifecycle, use ``renderOnce(content:)``:
```swift
import TUIKit
import TUIkit
renderOnce {
VStack {
Text("Hello, TUIKit!")
Text("Hello, TUIkit!")
.bold()
.foregroundColor(.green)
Divider()
@@ -1,10 +1,10 @@
# State Management
Manage reactive state in your TUIKit application.
Manage reactive state in your TUIkit application.
## Overview
TUIKit provides a state management system modeled after SwiftUI. When state changes, the view tree is automatically re-rendered.
TUIkit provides a state management system modeled after SwiftUI. When state changes, the view tree is automatically re-rendered.
## @State
@@ -99,7 +99,7 @@ struct SettingsView: View {
## How Re-Rendering Works
TUIKit uses a single-threaded event loop. When a ``State`` value changes:
TUIkit uses a single-threaded event loop. When a ``State`` value changes:
1. ``AppState/setNeedsRender()`` is called
2. The main loop detects the change
@@ -1,10 +1,10 @@
# Theming Guide
Customize the visual appearance of your TUIKit application with themes.
Customize the visual appearance of your TUIkit application with themes.
## Overview
TUIKit includes a full theming system with five built-in themes inspired by classic CRT terminals. Themes define semantic colors for backgrounds, foregrounds, accents, and UI elements.
TUIkit includes a full theming system with five built-in themes inspired by classic CRT terminals. Themes define semantic colors for backgrounds, foregrounds, accents, and UI elements.
## Built-in Themes
@@ -1,14 +1,14 @@
# ``TUIKit``
# ``TUIkit``
A declarative, SwiftUI-like framework for building Terminal User Interfaces in Swift.
@Metadata {
@DisplayName("TUIKit")
@DisplayName("TUIkit")
}
## Overview
TUIKit lets you build terminal applications using a familiar, declarative syntax inspired by SwiftUI. No ncurses, no C dependencies — pure Swift.
TUIkit lets you build terminal applications using a familiar, declarative syntax inspired by SwiftUI. No ncurses, no C dependencies — pure Swift.
```swift
@main
@@ -16,7 +16,7 @@ struct MyApp: App {
var body: some Scene {
WindowGroup {
VStack {
Text("Hello, TUIKit!")
Text("Hello, TUIkit!")
.bold()
.foregroundColor(.cyan)
Button("Press me") {
@@ -1,14 +1,14 @@
//
// TUIKit.swift
// TUIKit
// TUIkit.swift
// TUIkit
//
// A SwiftUI-like framework for Terminal User Interfaces.
//
// TUIKit enables creating TUI applications with a declarative,
// TUIkit enables creating TUI applications with a declarative,
// SwiftUI-like syntax - without ncurses or other low-level libraries.
//
/// The current version of TUIKit.
/// The current version of TUIkit.
public let tuiKitVersion = "0.1.0"
/// Executes a view closure and renders it once.
@@ -20,7 +20,7 @@ public let tuiKitVersion = "0.1.0"
/// ```swift
/// renderOnce {
/// VStack {
/// Text("Hello, TUIKit!")
/// Text("Hello, TUIkit!")
/// .bold()
/// .foregroundColor(.cyan)
/// Divider()
@@ -1,6 +1,6 @@
//
// Alert.swift
// TUIKit
// TUIkit
//
// A modal alert view with title, message, and optional action buttons.
//
@@ -1,6 +1,6 @@
//
// Box.swift
// TUIKit
// TUIkit
//
// A simple bordered container view.
//
@@ -1,6 +1,6 @@
//
// Button.swift
// TUIKit
// TUIkit
//
// An interactive button view that responds to keyboard input.
//
@@ -443,7 +443,7 @@ extension ButtonRow: Renderable {
// Render each button
var buttonBuffers: [FrameBuffer] = []
for button in buttons {
let buffer = TUIKit.renderToBuffer(button, context: context)
let buffer = TUIkit.renderToBuffer(button, context: context)
buttonBuffers.append(buffer)
}
@@ -1,6 +1,6 @@
//
// Card.swift
// TUIKit
// TUIkit
//
// A styled container view with optional header, content, and footer.
//
@@ -1,6 +1,6 @@
//
// ContainerView.swift
// TUIKit
// TUIkit
//
// A unified container component with Header/Body/Footer architecture.
//
@@ -283,13 +283,13 @@ extension ContainerView: Renderable {
// Render body content
let paddedContent = content.padding(padding)
let bodyBuffer = TUIKit.renderToBuffer(paddedContent, context: context)
let bodyBuffer = TUIkit.renderToBuffer(paddedContent, context: context)
// Render footer if present
let footerBuffer: FrameBuffer?
if let footerView = footer {
let paddedFooter = footerView.padding(EdgeInsets(horizontal: 1, vertical: 0))
footerBuffer = TUIKit.renderToBuffer(paddedFooter, context: context)
footerBuffer = TUIkit.renderToBuffer(paddedFooter, context: context)
} else {
footerBuffer = nil
}
@@ -1,6 +1,6 @@
//
// Dialog.swift
// TUIKit
// TUIkit
//
// A modal dialog view with title, custom content, and optional footer.
//
@@ -1,6 +1,6 @@
//
// ForEach.swift
// TUIKit
// TUIkit
//
// Iteration over data collections for view generation.
//
@@ -1,6 +1,6 @@
//
// Menu.swift
// TUIKit
// TUIkit
//
// A menu view that displays a list of selectable items.
//
@@ -429,7 +429,7 @@ public struct AnyView: View {
/// Creates an AnyView wrapping the given view.
public init<V: View>(_ view: V) {
self._render = { context in
TUIKit.renderToBuffer(view, context: context)
TUIkit.renderToBuffer(view, context: context)
}
}
@@ -1,6 +1,6 @@
//
// Panel.swift
// TUIKit
// TUIkit
//
// A titled container view with optional footer.
//
@@ -1,6 +1,6 @@
//
// Spacer.swift
// TUIKit
// TUIkit
//
// Flexible spacing elements for layout.
//
@@ -1,6 +1,6 @@
//
// Stacks.swift
// TUIKit
// TUIkit
//
// Layout containers for vertical and horizontal arrangement.
//
@@ -1,6 +1,6 @@
//
// StatusBar.swift
// TUIKit
// TUIkit
//
// A status bar that displays keyboard shortcuts and context-sensitive actions.
// Always rendered at the bottom of the terminal, never dimmed by overlays.
@@ -1,13 +1,13 @@
//
// Text.swift
// TUIKit
// TUIkit
//
// A view for displaying text in the terminal.
//
/// A view that displays text in the terminal.
///
/// `Text` is one of the most fundamental views in TUIKit. It displays
/// `Text` is one of the most fundamental views in TUIkit. It displays
/// a string in the terminal and supports various formatting options.
///
/// # Example
@@ -1,11 +1,11 @@
//
// AppState.swift
// TUIKitExample
// TUIkitExample
//
// Global state management for the example app.
//
import TUIKit
import TUIkit
// MARK: - Demo Page Enum
@@ -1,11 +1,11 @@
//
// DemoSection.swift
// TUIKitExample
// TUIkitExample
//
// A reusable section component for organizing demo content.
//
import TUIKit
import TUIkit
/// A section with a styled title and content.
///
@@ -1,11 +1,11 @@
//
// HeaderView.swift
// TUIKitExample
// TUIkitExample
//
// A reusable header component for demo pages.
//
import TUIKit
import TUIkit
/// A styled header with title on the left and version on the right.
///
@@ -36,7 +36,7 @@ struct HeaderView: View {
.bold()
.foregroundColor(.theme.accent)
Spacer()
Text("TUIKit v\(tuiKitVersion)")
Text("TUIkit v\(tuiKitVersion)")
.foregroundColor(.theme.foregroundTertiary)
}
if let subtitleText = subtitle {
@@ -1,11 +1,11 @@
//
// ContentView.swift
// TUIKitExample
// TUIkitExample
//
// The main content view that routes between demo pages.
//
import TUIKit
import TUIkit
// MARK: - Content View (Page Router)
@@ -1,11 +1,11 @@
//
// ButtonsPage.swift
// TUIKitExample
// TUIkitExample
//
// Demonstrates button and focus system capabilities.
//
import TUIKit
import TUIkit
/// Buttons and focus demo page.
///
@@ -1,11 +1,11 @@
//
// ColorsPage.swift
// TUIKitExample
// TUIkitExample
//
// Demonstrates color capabilities.
//
import TUIKit
import TUIkit
/// Colors demo page.
///
@@ -1,11 +1,11 @@
//
// ContainersPage.swift
// TUIKitExample
// TUIkitExample
//
// Demonstrates container view capabilities.
//
import TUIKit
import TUIkit
/// Container views demo page.
///
@@ -1,11 +1,11 @@
//
// LayoutPage.swift
// TUIKitExample
// TUIkitExample
//
// Demonstrates layout system capabilities.
//
import TUIKit
import TUIkit
/// Layout system demo page.
///
@@ -1,11 +1,11 @@
//
// MainMenuPage.swift
// TUIKitExample
// TUIkitExample
//
// The main menu page with navigation to all demos.
//
import TUIKit
import TUIkit
/// The main menu page.
///
@@ -17,7 +17,7 @@ struct MainMenuPage: View {
VStack(spacing: 1) {
HeaderView(
title: "TUIKit Example App",
title: "TUIkit Example App",
subtitle: "A SwiftUI-like framework for Terminal User Interfaces"
)
@@ -1,11 +1,11 @@
//
// OverlaysPage.swift
// TUIKitExample
// TUIkitExample
//
// Demonstrates overlay and modal capabilities.
//
import TUIKit
import TUIkit
/// Overlays and modals demo page.
///
@@ -1,11 +1,11 @@
//
// TextStylesPage.swift
// TUIKitExample
// TUIkitExample
//
// Demonstrates text styling capabilities.
//
import TUIKit
import TUIkit
/// Text styles demo page.
///
@@ -1,14 +1,14 @@
//
// main.swift
// TUIKitExample
// TUIkitExample
//
// Entry point for the TUIKit example application.
// Entry point for the TUIkit example application.
//
// This app demonstrates TUIKit capabilities through various demo pages.
// This app demonstrates TUIkit capabilities through various demo pages.
// Use the menu to navigate between demos.
//
import TUIKit
import TUIkit
// MARK: - Main App
@@ -1,13 +1,13 @@
//
// AppearanceTests.swift
// TUIKit
// TUIkit
//
// Tests for the Appearance system.
//
import Testing
@testable import TUIKit
@testable import TUIkit
// MARK: - Appearance Tests
@@ -1,13 +1,13 @@
//
// ButtonTests.swift
// TUIKit
// TUIkit
//
// Tests for Button, ButtonStyle, and ButtonRow views.
//
import Testing
@testable import TUIKit
@testable import TUIkit
// MARK: - Test Helpers
@@ -1,13 +1,13 @@
//
// ColorTests.swift
// TUIKit
// TUIkit
//
// Tests for the Color system and ANSI rendering.
//
import Testing
@testable import TUIKit
@testable import TUIkit
@Suite("Color Tests")
struct ColorTests {
@@ -1,13 +1,13 @@
//
// ContainerViewTests.swift
// TUIKit
// TUIkit
//
// Tests for container views: Alert, Dialog, Menu.
//
import Testing
@testable import TUIKit
@testable import TUIkit
@Suite("Alert Tests")
struct AlertTests {
@@ -1,6 +1,6 @@
//
// FocusTests.swift
// TUIKit
// TUIkit
//
// Tests for the focus management system.
//
@@ -8,7 +8,7 @@
import Foundation
import Testing
@testable import TUIKit
@testable import TUIkit
// MARK: - Mock Focusable
@@ -1,13 +1,13 @@
//
// FrameBufferTests.swift
// TUIKit
// TUIkit
//
// Tests for FrameBuffer operations and compositing.
//
import Testing
@testable import TUIKit
@testable import TUIkit
@Suite("FrameBuffer Tests")
struct FrameBufferTests {
@@ -1,13 +1,13 @@
//
// RenderingTests.swift
// TUIKit
// TUIkit
//
// Tests for view rendering and layout.
//
import Testing
@testable import TUIKit
@testable import TUIkit
@Suite("Rendering Tests")
struct RenderingTests {
@@ -1,14 +1,14 @@
// swiftlint:disable file_length
//
// StatusBarTests.swift
// TUIKit
// TUIkit
//
// Tests for Shortcut constants, StatusBarItem, StatusBarManager, and StatusBar.
//
import Testing
@testable import TUIKit
@testable import TUIkit
// MARK: - Shortcut Constants Tests
@@ -1,13 +1,13 @@
//
// ViewTests.swift
// TUIKit
// TUIkit
//
// Tests for the View protocol, ViewBuilder, and basic views.
//
import Testing
@testable import TUIKit
@testable import TUIkit
@Suite("View Protocol Tests")
struct ViewTests {
+5 -5
View File
@@ -1,4 +1,4 @@
# TUIKit - Comprehensive Project Analysis
# TUIkit - Comprehensive Project Analysis
**Date:** 2026-01-30
**Scope:** Full codebase review (66 Swift files)
@@ -8,7 +8,7 @@
## Executive Summary
TUIKit is a well-architected declarative Swift framework for building terminal UIs. The SwiftUI-inspired API design is clean, consistent, and idiomatic. Documentation is above average for a personal project. However, the codebase suffers from **significant code duplication** across Views and Modifiers, an **overreliance on singletons** that hinders testability, and several **thread-safety issues** with `@unchecked Sendable` types. The most impactful improvements would be extracting shared rendering logic into utilities, replacing singletons with dependency injection, and expanding test coverage for Views and Modifiers.
TUIkit is a well-architected declarative Swift framework for building terminal UIs. The SwiftUI-inspired API design is clean, consistent, and idiomatic. Documentation is above average for a personal project. However, the codebase suffers from **significant code duplication** across Views and Modifiers, an **overreliance on singletons** that hinders testability, and several **thread-safety issues** with `@unchecked Sendable` types. The most impactful improvements would be extracting shared rendering logic into utilities, replacing singletons with dependency injection, and expanding test coverage for Views and Modifiers.
---
@@ -319,7 +319,7 @@ Marked as "Fixed Frame Modifier (Legacy)". `FlexibleFrameView` is the active imp
---
### C.5 TODO Placeholder — `TUIKit.swift` (Low)
### C.5 TODO Placeholder — `TUIkit.swift` (Low)
```swift
return 0 // TODO: Return actual line count
@@ -818,14 +818,14 @@ The same bug exists in `AppearanceManager.setAppearance()`.
| C.2 | `FrameModifier` legacy code | Dead Code | Medium | FrameModifier.swift |
| C.3 | Panel `body` dead code | Dead Code | Medium | Panel.swift |
| C.4 | Common Preference Keys likely unused | Dead Code | Low | Preferences.swift |
| C.5 | TODO placeholder return 0 | Dead Code | Low | TUIKit.swift |
| C.5 | TODO placeholder return 0 | Dead Code | Low | TUIkit.swift |
| C.6 | Menu pointless ternary `4 : 4` | Dead Code | Low | Menu.swift |
| C.7 | Menu identical if/else branches | Dead Code | Low | Menu.swift |
| C.8 | `_ = self` anti-pattern | Dead Code | Low | App.swift |
| E.1 | Public types missing doc comments | Documentation | Medium | Multiple |
| E.2 | Complex logic without inline comments | Documentation | Medium | KeyEvent, FrameBuffer, ViewRenderer |
| E.3 | Missing contributor documentation | Documentation | Medium | Project root |
| E.4 | Example app missing demonstrations | Documentation | Low | TUIKitExample |
| E.4 | Example app missing demonstrations | Documentation | Low | TUIkitExample |
| F.1 | 60+ magic hex values in KeyEvent | Constants | High | KeyEvent.swift |
| F.2 | ANSI style codes as magic strings | Constants | Medium | ANSIRenderer.swift |
| F.3 | Block characters scattered | Constants | Medium | Multiple |