Skip to content

Conversation

@sungchul2
Copy link

@sungchul2 sungchul2 commented Jan 7, 2026

Summary

  • Add table-formatter hook to fix markdown table column misalignment in OpenCode TUI
  • Use string-width library for Unicode-aware column width calculation (CJK, emoji support)
  • Hook uses experimental.text.complete to format tables after LLM response, before rendering

Problems

LLM-generated markdown tables display with misaligned columns in OpenCode TUI:
(I captured below images during work, so some columns are skipped due to security issue)

Before After
image image

Changes

  • Add src/hooks/table-formatter/ with parser, formatter, and tests
  • Add string-width dependency for Unicode width calculation
  • Register hook in src/index.ts and config schema
  • Handle alignment markers (:) correctly in separator rows
  • Handle leading whitespace in table rows

Addressed Review Comments (c936856, 291adaf)

  • Add try/catch error handling to prevent session crash on malformed input (P1)
  • Fix row formatting to iterate over columnCount instead of row.map() for proper alignment when rows have fewer cells than headers (P2)
  • Strengthen test assertions to verify actual formatting output

Known Issues

⚠️ OpenCode TUI Rendering Issue: The hook correctly formats tables (verified by logs), but the TUI may not display the formatted output due to a rendering issue in OpenCode.

Tracked in: anomalyco/opencode#7287

The hook IS working - logs confirm tables are being parsed and formatted. However, the TUI appears to cache streamed text-delta updates and may not refresh when the final formatted text is saved after text-end.

Testing

  • bun run typecheck passes
  • bun run build succeeds
  • 22 unit tests pass
  • Tested locally with OpenCode using Claude, GPT, MiniMax, and GLM
  • Hook execution verified via plugin logs

Checklist

  • Code follows project conventions
  • bun run typecheck passes
  • bun run build succeeds
  • Tested locally with OpenCode
  • Updated documentation (AGENTS.md)
  • No version changes in package.json

- Add experimental.text.complete hook to format markdown tables
- Use string-width for Unicode-aware column width calculation
- Handle CJK characters, emoji, and alignment markers correctly
- Register hook in disabled_hooks config schema
@github-actions
Copy link
Contributor

github-actions bot commented Jan 7, 2026

All contributors have signed the CLA. Thank you! ✅
Posted by the CLA Assistant Lite bot.

@sungchul2
Copy link
Author

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Jan 7, 2026
@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Greptile Summary

This PR adds a table-formatter hook that automatically formats LLM-generated markdown tables for proper column alignment in OpenCode TUI. The implementation uses the string-width library for Unicode-aware width calculation, correctly handling CJK characters and emoji that occupy multiple display columns.

Key changes:

  • Implemented parseMarkdownTables() to extract tables with headers, alignments (:---:, ---:, :---), and data rows
  • Created formatTable() to calculate optimal column widths and apply left/center/right alignment with proper padding
  • Integrated via experimental.text.complete hook to process text after LLM response but before TUI rendering
  • Added 21 comprehensive unit tests covering edge cases (CJK, emoji, multiple tables, alignments)
  • Updated config schema and documentation following project conventions

Implementation quality:

  • Clean separation: parser → formatter → hook integration
  • TDD approach with BDD-style test comments (#given, #when, #then)
  • Follows project patterns: kebab-case directories, factory pattern (createTableFormatterHook), barrel exports
  • Proper error handling: returns early when no tables found, uses optional chaining
  • Zero version bump (correct per deployment guidelines)

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation follows all project conventions, has comprehensive test coverage (21 tests), solves a real UX issue, and integrates cleanly without modifying existing code paths. The hook is optional and can be disabled via config.
  • No files require special attention

Important Files Changed

Filename Overview
src/hooks/table-formatter/parser.ts Parses markdown tables from text, extracting headers, alignments, and rows with proper index tracking
src/hooks/table-formatter/formatter.ts Formats markdown tables with Unicode-aware column alignment using string-width library
src/hooks/table-formatter/index.ts Main hook implementation using experimental.text.complete to format LLM-generated tables
src/hooks/table-formatter/parser.test.ts Comprehensive tests for parser: basic tables, alignments, multiple tables, CJK, emoji, edge cases
src/hooks/table-formatter/formatter.test.ts Tests for formatter: column padding, alignments, CJK width calculation, text replacement
src/hooks/table-formatter/index.test.ts Integration tests for hook: LLM response formatting, multiple tables, CJK, emoji handling
src/index.ts Integrated table-formatter hook with experimental.text.complete lifecycle event

Sequence Diagram

sequenceDiagram
    participant LLM as LLM Response
    participant Plugin as OhMyOpenCode Plugin
    participant Hook as TableFormatterHook
    participant Parser as Parser
    participant Formatter as Formatter
    participant TUI as OpenCode TUI

    LLM->>Plugin: Generate markdown table text
    Plugin->>Hook: experimental.text.complete(output)
    Hook->>Parser: parseMarkdownTables(text)
    Parser->>Parser: Identify table rows with TABLE_ROW_REGEX
    Parser->>Parser: Find separator rows (---:)
    Parser->>Parser: Extract headers, alignments, rows
    Parser->>Parser: Calculate startIndex/endIndex
    Parser-->>Hook: ParsedTable[]
    
    alt Tables found
        Hook->>Formatter: formatTablesInText(text, tables)
        loop For each table (reverse order)
            Formatter->>Formatter: Calculate column widths with stringWidth()
            Formatter->>Formatter: Pad cells (left/center/right alignment)
            Formatter->>Formatter: Create separator with alignment markers
            Formatter->>Formatter: Replace original table with formatted
        end
        Formatter-->>Hook: Formatted text
        Hook->>Plugin: Mutate output.text
    else No tables
        Hook->>Plugin: Return unchanged
    end
    
    Plugin->>TUI: Display formatted table
    TUI->>TUI: Render with proper CJK/emoji alignment
Loading

@greptile-apps
Copy link

greptile-apps bot commented Jan 7, 2026

Greptile found no issues!

From now on, if a review finishes and we haven't found any issues, we will not post anything, but you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 14 files

Confidence score: 3/5

  • Missing try/catch in src/hooks/table-formatter/index.ts leaves parsing and formatting errors uncaught, so malformed markdown can crash the hook instead of failing gracefully.
  • src/hooks/table-formatter/formatter.ts only maps existing cells, so rows with fewer cells than headers will misalign columns despite width computation accounting for all columns.
  • Pay close attention to src/hooks/table-formatter/index.ts, src/hooks/table-formatter/formatter.ts - add error handling and ensure rows pad out to header width to avoid crashes and misaligned output.
Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/hooks/table-formatter/formatter.test.ts">

<violation number="1" location="src/hooks/table-formatter/formatter.test.ts:88">
P2: Test only verifies separator row format but doesn't check that data rows are actually centered. Unlike the right alignment test which verifies data row alignment (`|   100 |`), this test doesn't verify that 'OK' and 'ERROR' are centered in the output. Consider adding assertions for data row centering.</violation>
</file>

<file name="src/hooks/table-formatter/index.test.ts">

<violation number="1" location="src/hooks/table-formatter/index.test.ts:66">
P2: Test assertions don't verify formatting occurred. These assertions check for strings that already exist unchanged in the input, so they would pass even if the hook did nothing. Consider asserting on padded values (e.g., in test 4, check that `| KR  |` appears since `KR` should be padded to width 3 to match `KR1`/`KR5`).</violation>
</file>

<file name="src/hooks/table-formatter/formatter.ts">

<violation number="1" location="src/hooks/table-formatter/formatter.ts:74">
P2: Row formatting uses `row.map()` which only iterates over actual cells in the row. If a row has fewer cells than headers, the output will have misaligned columns. The width calculation already handles this with `row[col] ?? ""`, but the formatting should iterate over `columnCount` instead.</violation>
</file>

<file name="src/hooks/table-formatter/parser.test.ts">

<violation number="1" location="src/hooks/table-formatter/parser.test.ts:134">
P2: Incomplete test coverage: the second row with the ZWJ emoji (`👨‍👩‍👧`) is not verified. ZWJ emoji sequences are more complex than simple emojis and are a common edge case for Unicode width calculation. Consider adding an assertion for `rows[1]` to ensure the parser handles ZWJ emojis correctly.</violation>
</file>

<file name="src/hooks/table-formatter/index.ts">

<violation number="1" location="src/hooks/table-formatter/index.ts:8">
P1: Missing try/catch wrapper around parsing/formatting operations. According to the hooks AGENTS.md anti-patterns, hooks should have error handling to avoid crashing the session. The `parseMarkdownTables` and `formatTablesInText` calls could throw on malformed input.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

- Add try/catch to prevent session crash on malformed input (P1)
- Fix row.map() to iterate over columnCount for proper alignment
  when rows have fewer cells than headers (P2)
- Add center alignment data row verification (formatter.test.ts)
- Verify actual padding in multiple tables test (index.test.ts)
- Verify padded CJK and emoji handling (index.test.ts)
- Add ZWJ emoji row verification (parser.test.ts)
- Add test for rows with fewer cells than headers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant