Skip to content

Commit 0a205cb

Browse files
Merge pull request #9 from TimeWarpEngineering/Cramer/2025-12-22/dev
feat: Restructure dev-cli, add agent context, fix emoji alignment
2 parents 5dfeb23 + 69e2674 commit 0a205cb

64 files changed

Lines changed: 1915 additions & 2801 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.envrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Add local bin directory to PATH for dev CLI
2+
export PATH="$PWD/bin:$PATH"

.github/workflows/ci-cd.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ jobs:
5151
- name: Run CI Pipeline
5252
run: |
5353
if [ "${{ github.event_name }}" == "release" ]; then
54-
dotnet run --project tools/dev-cli/timewarp-terminal-dev-cli.csproj -- ci --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}"
54+
dotnet run tools/dev-cli/dev.cs -- ci --api-key "${{ steps.nuget-login.outputs.NUGET_API_KEY }}"
5555
else
56-
dotnet run --project tools/dev-cli/timewarp-terminal-dev-cli.csproj -- ci
56+
dotnet run tools/dev-cli/dev.cs -- ci
5757
fi
5858
5959
- name: Upload Artifacts

Directory.Build.props

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@
3232
<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />
3333
</ItemGroup>
3434

35+
<!-- C# Interceptors (for source generators) -->
36+
<PropertyGroup Label="Interceptors">
37+
<!-- Enable interceptors in the TimeWarp.Nuru.Generated namespace -->
38+
<!-- Note: Property name changed from InterceptorsPreviewNamespaces to InterceptorsNamespaces in .NET 10 -->
39+
<InterceptorsNamespaces>$(InterceptorsNamespaces);TimeWarp.Nuru.Generated</InterceptorsNamespaces>
40+
</PropertyGroup>
41+
3542
<!-- Code quality, analyzers, and warning configuration -->
3643
<PropertyGroup Label="Code Quality and Analysis">
3744
<!-- Treat all warnings as errors -->

Directory.Packages.props

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@
2525
<PackageVersion Include="TimeWarp.Amuru" Version="1.0.0-beta.17" />
2626
<PackageVersion Include="TimeWarp.Builder" Version="1.0.0-beta.1" />
2727
<PackageVersion Include="TimeWarp.Terminal" Version="1.0.0-beta.2" />
28-
<PackageVersion Include="TimeWarp.Jaribu" Version="1.0.0-beta.8" />
28+
<PackageVersion Include="TimeWarp.Jaribu" Version="1.0.0-beta.9" />
2929
<PackageVersion Include="TimeWarp.Build.Tasks" Version="1.0.0" />
3030
<PackageVersion Include="TimeWarp.OptionsValidation" Version="1.0.0-beta.4" />
31-
<PackageVersion Include="TimeWarp.Nuru" Version="3.0.0-beta.23" />
32-
<PackageVersion Include="TimeWarp.Nuru.Parsing" Version="$(Version)" />
33-
<PackageVersion Include="TimeWarp.Nuru.Analyzers" Version="$(Version)" />
31+
<PackageVersion Include="TimeWarp.Nuru" Version="3.0.0-beta.47" />
3432
</ItemGroup>
3533
<ItemGroup Label="Mediator - martinothamar source-generator based">
3634
<PackageVersion Include="Mediator.Abstractions" Version="3.1.0-preview.14" />

documentation/dependents.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Downstream Dependents
2+
3+
Repos that depend on `TimeWarp.Terminal`. When making breaking changes, create a GitHub issue in each repo to notify them of the update.
4+
5+
## Known Dependents
6+
7+
| Repository | Dependency Type | Notes |
8+
|------------|----------------|-------|
9+
| [timewarp-nuru](https://github.com/TimeWarpEngineering/timewarp-nuru) | NuGet | Uses `IConsole`, `ITerminal`, `TimeWarpConsole`, `TestTerminal` |
10+
| [timewarp-jaribu](https://github.com/TimeWarpEngineering/timewarp-jaribu) | NuGet | Uses `WriteTable` for test results rendering |
11+
| [timewarp-builder](https://github.com/TimeWarpEngineering/timewarp-builder) | NuGet | |
12+
| [timewarp-ganda](https://github.com/TimeWarpEngineering/timewarp-ganda) | NuGet | |
13+
| [crunchit](https://github.com/TimeWarpEngineering/crunchit) | NuGet | |
14+
| [timewarp-flow](https://github.com/TimeWarpEngineering/timewarp-flow) | Skill/docs | Terminal skill in `opencode/skills/terminal/SKILL.md` |
15+
16+
## Discovering New Dependents
17+
18+
Search across TimeWarp repos for `TimeWarp.Terminal` package references:
19+
20+
```bash
21+
gh search code "TimeWarp.Terminal" --owner TimeWarpEngineering --filename Directory.Packages.props
22+
gh search code "TimeWarp.Terminal" --owner TimeWarpEngineering --filename "*.csproj"
23+
```
24+
25+
## Notification Template
26+
27+
When publishing a breaking release, create issues with:
28+
29+
```
30+
Title: Update TimeWarp.Terminal to <version>
31+
Body:
32+
TimeWarp.Terminal <version> has been published with breaking changes:
33+
- <list changes>
34+
35+
Please update your `Directory.Packages.props` to reference the new version.
36+
```
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Fix emoji alignment issue in table borders
2+
3+
## Description
4+
5+
Emojis in table content cause misalignment with the table borders. When emojis are used in cells (like weather icons 🌤️, 📍, 🌡️, ☁️), the table border lines don't line up properly with the content, creating a visual misalignment.
6+
7+
## Example
8+
9+
Sample output showing the problem:
10+
```
11+
═════════════════════════════════════════ 🌤️ Weather Report ═════════════════════════════════════════
12+
╭─────────────────┬───────────────────────╮
13+
│ 📍 Location │ Dallas, United States │
14+
│ 🌡️ Temperature │ 25.2°C (77.4°F) │
15+
│ ☁️ Condition │ Overcast │
16+
╰─────────────────┴───────────────────────╯
17+
```
18+
19+
## Root Cause
20+
21+
`AnsiStringUtils.GetVisibleLength()` returns `StripAnsiCodes(text).Length` which counts .NET string length (UTF-16 code units), not terminal display columns. Emojis like `📍` take 2 terminal columns but `.Length` counts them as 1-2 characters, making borders too short.
22+
23+
## Implementation
24+
25+
Added `UnicodeWidth` utility class that calculates terminal display width accounting for wide characters (emoji, CJK) and zero-width characters (combining marks, ZWJ). Updated `GetVisibleLength()` to use it. No external dependencies — uses .NET 10 built-in `Rune`, `StringInfo`, and `UnicodeCategory` APIs (all AOT-compatible).
26+
27+
### Wide character ranges covered (width 2)
28+
29+
**Emoji_Presentation=Yes (BMP, from Unicode 16.0 emoji-data.txt):**
30+
- U+231A-231B ⌚⌛, U+23E9-23EC ⏩⏪⏫⏬, U+23F0 ⏰, U+23F3 ⏳
31+
- U+25FD-25FE ◽◾
32+
- U+2614-2615 ☔☕, U+2648-2653 ♈-♓, U+267F ♿, U+2693 ⚓, U+26A1 ⚡
33+
- U+26AA-26AB ⚪⚫, U+26BD-26BE ⚽⚾, U+26C4-26C5 ⛄⛅, U+26CE ⛎
34+
- U+26D4 ⛔, U+26EA ⛪, U+26F2-26F3 ⛲⛳, U+26F5 ⛵, U+26FA ⛺, U+26FD ⛽
35+
- U+2705 ✅, U+270A-270B ✊✋, U+2728 ✨, U+274C ❌, U+274E ❎
36+
- U+2753-2755 ❓❔❕, U+2757 ❗, U+2795-2797 ➕➖➗, U+27B0 ➰, U+27BF ➿
37+
- U+2B1B-2B1C ⬛⬜, U+2B50 ⭐, U+2B55 ⭕
38+
39+
**Text-presentation characters (☀♻✈▶◀▪▫ etc.)** are width 1 as single
40+
runes. They become width 2 when combined with VS16 (U+FE0F) as
41+
multi-codepoint grapheme clusters, handled automatically by GetTextWidth.
42+
43+
**Emoji blocks (SMP):**
44+
- U+1F000-U+1FAFF, U+1FC00-U+1FFFF — Emoji blocks
45+
- U+1F1E0-U+1F1FF — Regional indicator symbols (flags)
46+
47+
**CJK:**
48+
- U+2E80-U+303E — CJK Radicals, Kangxi, Ideographic Description
49+
- U+3041-U+33BF — Hiragana, Katakana, Bopomofo, CJK Compatibility
50+
- U+3400-U+4DBF — CJK Extension A
51+
- U+4E00-U+9FFF — CJK Unified Ideographs
52+
- U+A000-U+A4CF — Yi Syllables and Radicals
53+
- U+AC00-U+D7A3 — Hangul Syllables
54+
- U+F900-U+FAFF — CJK Compatibility Ideographs
55+
- U+FE10-U+FE19 — Vertical Forms
56+
- U+FE30-U+FE6F — CJK Compatibility Forms
57+
- U+FF01-U+FF60, U+FFE0-U+FFE6 — Fullwidth forms
58+
- U+1100-U+115F — Hangul Jamo
59+
- U+2329-U+232A — Wide angle brackets
60+
- U+20000-U+2A6DF — CJK Extension B
61+
- U+2A700-U+2B73F — CJK Extension C
62+
- U+2B740-U+2B81F — CJK Extension D
63+
- U+2B820-U+2CEAF — CJK Extension E
64+
- U+2CEB0-U+2EBEF — CJK Extension F
65+
- U+2F800-U+2FA1F — CJK Compatibility Ideographs Supplement
66+
- U+30000-U+3134F — CJK Extension G
67+
- U+31350-U+323AF — CJK Extension H
68+
69+
**Zero-width (width 0):**
70+
- Control characters
71+
- UnicodeCategory.NonSpacingMark, EnclosingMark, Format
72+
- U+00AD (soft hyphen), U+200B-U+200D (ZWSP, ZWNJ, ZWJ), U+2060 (WJ)
73+
- U+FE00-U+FE0F (variation selectors), U+E0100-U+E01EF (VS supplement)
74+
75+
**Multi-codepoint grapheme clusters** (ZWJ sequences, flags, skin-tone) → width 2
76+
77+
### Files changed
78+
79+
- `source/timewarp-terminal/widgets/unicode-width.cs` (new)
80+
- `source/timewarp-terminal/widgets/ansi-string-utils.cs` (modified)
81+
- `source/timewarp-terminal/widgets/table-widget.cs` (modified)
82+
- `source/timewarp-terminal/global-usings.cs` (modified)
83+
- `samples/emoji-table-widget.cs` (new)
84+
- `tests/unicode-width-01-basic.cs` (new)
85+
- `tests/ansi-string-utils-03-emoji-width.cs` (new)
86+
- `tests/table-widget-06-emoji.cs` (new)
87+
88+
## Checklist
89+
90+
- [x] Investigate the table rendering code to understand how cell width calculations work
91+
- [x] Identify why emojis cause border misalignment (emoji display width vs character count)
92+
- [x] Research proper emoji width handling (full-width vs half-width emojis)
93+
- [x] Implement UnicodeWidth utility class with comprehensive ranges
94+
- [x] Update GetVisibleLength to use display width
95+
- [x] Update WrapText for grapheme-cluster-aware iteration
96+
- [x] Update TruncateWithEllipsis for display-width-aware slicing
97+
- [x] Add tests for UnicodeWidth, AnsiStringUtils emoji, and table emoji rendering
98+
- [x] Verify tables without emojis still render correctly (92 tests pass)
99+
- [x] Visually verify weather report table alignment

samples/emoji-table-widget.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/dotnet --
2+
#:project ../source/timewarp-terminal/timewarp-terminal.csproj
3+
4+
// Demonstrates emoji and wide character alignment in table/panel/rule borders
5+
using TimeWarp.Terminal;
6+
7+
TimeWarpTerminal terminal = new();
8+
9+
// ── Emoji blocks (U+1F000-U+1FAFF) ──
10+
terminal
11+
.WriteRule("🌤️ Weather Report", style: LineStyle.Doubled)
12+
.WriteTable(t => t
13+
.AddColumn("Info")
14+
.AddColumn("Value")
15+
.AddRow("📍 Location", "Dallas, United States")
16+
.AddRow("🌡️ Temperature", "25.2°C (77.4°F)")
17+
.AddRow("☁️ Condition", "Overcast")
18+
.Border(BorderStyle.Rounded))
19+
.WriteLine();
20+
21+
// ── Emoji_Presentation=Yes (width 2 without VS16) ──
22+
terminal
23+
.WriteRule("Default Emoji Presentation")
24+
.WriteTable(t => t
25+
.AddColumn("Symbol")
26+
.AddColumn("Name")
27+
.AddColumn("Range")
28+
.AddRow("⚡ Zap", "U+26A1", "Misc Symbols")
29+
.AddRow("✅ Check", "U+2705", "Dingbats")
30+
.AddRow("❌ Cross", "U+274C", "Dingbats")
31+
.AddRow("⭐ Star", "U+2B50", "Misc Sym+Arrows")
32+
.AddRow("⬛ Black", "U+2B1B", "Misc Sym+Arrows")
33+
.AddRow("☔ Rain", "U+2614", "Misc Symbols")
34+
.AddRow("⌚ Watch", "U+231A", "Misc Technical")
35+
.AddRow("⏰ Alarm", "U+23F0", "Misc Technical")
36+
.AddRow("➕ Plus", "U+2795", "Dingbats")
37+
.AddRow("❗ Exclaim", "U+2757", "Dingbats")
38+
.Border(BorderStyle.Rounded))
39+
.WriteLine();
40+
41+
// ── Text presentation + VS16 (width 2 only with ️) ──
42+
terminal
43+
.WriteRule("Text Presentation + VS16")
44+
.WriteTable(t => t
45+
.AddColumn("With VS16")
46+
.AddColumn("Name")
47+
.AddRow("☀️ Sun", "U+2600 + U+FE0F")
48+
.AddRow("♻️ Recycle", "U+267B + U+FE0F")
49+
.AddRow("✈️ Plane", "U+2708 + U+FE0F")
50+
.AddRow("▶️ Play", "U+25B6 + U+FE0F")
51+
.AddRow("☁️ Cloud", "U+2601 + U+FE0F")
52+
.Border(BorderStyle.Rounded))
53+
.WriteLine();
54+
55+
// ── CJK Characters ──
56+
terminal
57+
.WriteRule("CJK Characters")
58+
.WriteTable(t => t
59+
.AddColumn("Text")
60+
.AddColumn("Range")
61+
.AddRow("漢字", "CJK Unified (U+4E00)")
62+
.AddRow("ひらがな", "Hiragana (U+3041)")
63+
.AddRow("カタカナ", "Katakana (U+30A0)")
64+
.AddRow("한글", "Hangul (U+AC00)")
65+
.Border(BorderStyle.Rounded))
66+
.WriteLine();
67+
68+
// ── Fullwidth Forms ──
69+
terminal
70+
.WriteRule("Fullwidth Forms")
71+
.WriteTable(t => t
72+
.AddColumn("Fullwidth")
73+
.AddColumn("Normal")
74+
.AddRow("ABCD", "ABCD")
75+
.AddRow("1234", "1234")
76+
.Border(BorderStyle.Rounded))
77+
.WriteLine();
78+
79+
// ── Multi-codepoint grapheme clusters (ZWJ, flags, skin tones) ──
80+
terminal
81+
.WriteRule("Multi-Codepoint Clusters")
82+
.WriteTable(t => t
83+
.AddColumn("Emoji")
84+
.AddColumn("Type")
85+
.AddRow("🇺🇸 Flag", "Regional Indicators")
86+
.AddRow("👋🏽 Wave", "Skin Tone Modified")
87+
.AddRow("🌤️ Cloud", "Variation Selector")
88+
.Border(BorderStyle.Rounded))
89+
.WriteLine();
90+
91+
// ── Panel with mixed content ──
92+
terminal
93+
.WritePanel(p => p
94+
.Header("💠 Status Dashboard")
95+
.Content("✅ API online ❌ DB offline ⏳ Cache warming ⭐ 99.9% uptime")
96+
.Border(BorderStyle.Rounded))
97+
.WriteLine()
98+
.WriteLine("Demo complete!");

samples/hyperlink-widget.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/dotnet --
22
// hyperlink-widget-demo - Demonstrates OSC 8 hyperlinks in terminal output
33
// GitHub Issue: https://github.com/TimeWarpEngineering/timewarp-terminal/issues/95
4-
#:project ../../source/timewarp-terminal/timewarp-terminal.csproj
4+
#:project ../source/timewarp-terminal/timewarp-terminal.csproj
55

66
using TimeWarp.Terminal;
77

88
// Get a terminal instance
9-
ITerminal terminal = TimeWarpTerminal.Default;
9+
TimeWarpTerminal terminal = TimeWarpTerminal.Default;
1010

1111
terminal
1212
.WriteLine()

samples/panel-widget.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/dotnet --
22
// panel-widget-demo - Demonstrates the Panel widget for bordered boxes
33
// GitHub Issue: https://github.com/TimeWarpEngineering/timewarp-terminal/issues/90
4-
#:project ../../source/timewarp-terminal/timewarp-terminal.csproj
4+
#:project ../source/timewarp-terminal/timewarp-terminal.csproj
55

66
using TimeWarp.Terminal;
77

88
// Get a terminal instance
9-
ITerminal terminal = TimeWarpTerminal.Default;
9+
TimeWarpTerminal terminal = TimeWarpTerminal.Default;
1010

1111
terminal
1212
.WriteLine()

samples/rule-widget.cs

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#!/usr/bin/dotnet --
22
// rule-widget-demo - Demonstrates the Rule widget for horizontal divider lines
33
// GitHub Issue: https://github.com/TimeWarpEngineering/timewarp-terminal/issues/89
4-
#:project ../../source/timewarp-terminal/timewarp-terminal.csproj
4+
#:project ../source/timewarp-terminal/timewarp-terminal.csproj
55

66
using TimeWarp.Terminal;
77

88
// Get a terminal instance
9-
ITerminal terminal = TimeWarpTerminal.Default;
9+
TimeWarpTerminal terminal = TimeWarpTerminal.Default;
1010

1111
terminal
1212
.WriteLine()
@@ -46,41 +46,17 @@
4646
.WriteRule("Heavy Style", LineStyle.Heavy)
4747
.WriteLine();
4848

49-
// Fluent builder API
49+
// Colored rules using styled text
5050
terminal
51-
.WriteLine("5. Fluent builder API:")
52-
.WriteRule(rule => rule
53-
.Title("Configuration")
54-
.Style(LineStyle.Doubled)
55-
.Color(AnsiColors.Cyan))
56-
.WriteLine();
57-
58-
// Colored rules
59-
terminal
60-
.WriteLine("6. Colored rules:")
61-
.WriteRule(rule => rule
62-
.Title("Success".Green())
63-
.Color(AnsiColors.Green))
64-
.WriteRule(rule => rule
65-
.Title("Warning".Yellow())
66-
.Color(AnsiColors.Yellow))
67-
.WriteRule(rule => rule
68-
.Title("Error".Red())
69-
.Color(AnsiColors.Red))
70-
.WriteLine();
71-
72-
// Pre-configured rule via builder
73-
terminal
74-
.WriteLine("7. Pre-configured Rule via builder:")
75-
.WriteRule(rule => rule
76-
.Title("Custom Configuration")
77-
.Style(LineStyle.Heavy)
78-
.Color(AnsiColors.Magenta))
51+
.WriteLine("5. Colored rules (using styled text):")
52+
.WriteRule("Success".Green())
53+
.WriteRule("Warning".Yellow())
54+
.WriteRule("Error".Red())
7955
.WriteLine();
8056

8157
// Practical example — fluent chaining
8258
terminal
83-
.WriteLine("8. Practical example - fluent chaining:")
59+
.WriteLine("6. Practical example - fluent chaining:")
8460
.WriteLine()
8561
.WriteRule("Build Output")
8662
.WriteLine(" Compiling project...")
@@ -89,13 +65,6 @@
8965
.WriteRule("Test Results", LineStyle.Doubled)
9066
.WriteLine(" ✓ 42 tests passed")
9167
.WriteLine(" ✗ 0 tests failed")
92-
.WriteLine()
93-
.WriteRule(rule => rule
94-
.Title("Summary".Bold())
95-
.Style(LineStyle.Heavy)
96-
.Color(AnsiColors.BrightGreen))
97-
.WriteLine(" Total time: 1.23s")
98-
.WriteLine(" Status: " + "SUCCESS".Green().Bold())
9968
.WriteLine();
10069

10170
return 0;

0 commit comments

Comments
 (0)