@@ -676,3 +676,197 @@ export const ActiveWorkspaceWithChat: Story = {
676676 return < AppWithChatMocks /> ;
677677 } ,
678678} ;
679+
680+ /**
681+ * Story demonstrating markdown table rendering
682+ * Shows various table formats without disruptive copy/download actions
683+ */
684+ export const MarkdownTables : Story = {
685+ render : ( ) => {
686+ const AppWithTableMocks = ( ) => {
687+ const initialized = useRef ( false ) ;
688+
689+ if ( ! initialized . current ) {
690+ const workspaceId = "my-app-feature" ;
691+
692+ const workspaces : FrontendWorkspaceMetadata [ ] = [
693+ {
694+ id : workspaceId ,
695+ name : "feature" ,
696+ projectPath : "/home/user/projects/my-app" ,
697+ projectName : "my-app" ,
698+ namedWorkspacePath : "/home/user/.cmux/src/my-app/feature" ,
699+ } ,
700+ ] ;
701+
702+ setupMockAPI ( {
703+ projects : new Map ( [
704+ [
705+ "/home/user/projects/my-app" ,
706+ {
707+ workspaces : [
708+ { path : "/home/user/.cmux/src/my-app/feature" , id : workspaceId , name : "feature" } ,
709+ ] ,
710+ } ,
711+ ] ,
712+ ] ) ,
713+ workspaces,
714+ selectedWorkspaceId : workspaceId ,
715+ apiOverrides : {
716+ workspace : {
717+ create : ( projectPath : string , branchName : string ) =>
718+ Promise . resolve ( {
719+ success : true ,
720+ metadata : {
721+ id : Math . random ( ) . toString ( 36 ) . substring ( 2 , 12 ) ,
722+ name : branchName ,
723+ projectPath,
724+ projectName : projectPath . split ( "/" ) . pop ( ) ?? "project" ,
725+ namedWorkspacePath : `/mock/workspace/${ branchName } ` ,
726+ } ,
727+ } ) ,
728+ list : ( ) => Promise . resolve ( workspaces ) ,
729+ rename : ( workspaceId : string ) =>
730+ Promise . resolve ( {
731+ success : true ,
732+ data : { newWorkspaceId : workspaceId } ,
733+ } ) ,
734+ remove : ( ) => Promise . resolve ( { success : true } ) ,
735+ fork : ( ) => Promise . resolve ( { success : false , error : "Not implemented in mock" } ) ,
736+ openTerminal : ( ) => Promise . resolve ( undefined ) ,
737+ onChat : ( workspaceId , callback ) => {
738+ setTimeout ( ( ) => {
739+ // User message
740+ callback ( {
741+ id : "msg-1" ,
742+ role : "user" ,
743+ parts : [ { type : "text" , text : "Show me some table examples" } ] ,
744+ metadata : {
745+ historySequence : 1 ,
746+ timestamp : STABLE_TIMESTAMP ,
747+ } ,
748+ } ) ;
749+
750+ // Assistant message with tables
751+ callback ( {
752+ id : "msg-2" ,
753+ role : "assistant" ,
754+ parts : [
755+ {
756+ type : "text" ,
757+ text : `Here are various markdown table examples:
758+
759+ ## Simple Table
760+
761+ | Column 1 | Column 2 | Column 3 |
762+ |----------|----------|----------|
763+ | Value A | Value B | Value C |
764+ | Value D | Value E | Value F |
765+ | Value G | Value H | Value I |
766+
767+ ## Table with Different Alignments
768+
769+ | Left Aligned | Center Aligned | Right Aligned |
770+ |:-------------|:--------------:|--------------:|
771+ | Left | Center | Right |
772+ | Text | Text | Text |
773+ | More | Data | Here |
774+
775+ ## Code and Links in Tables
776+
777+ | Feature | Status | Notes |
778+ |---------|--------|-------|
779+ | \`markdown\` support | ✅ Done | Full GFM support |
780+ | [Links](https://example.com) | ✅ Done | Opens externally |
781+ | **Bold** and _italic_ | ✅ Done | Standard formatting |
782+
783+ ## Large Table with Many Rows
784+
785+ | ID | Name | Email | Status | Role | Last Login |
786+ |----|------|-------|--------|------|------------|
787+ | 1 | Alice Smith | alice@example.com | Active | Admin | 2024-01-20 |
788+ | 2 | Bob Jones | bob@example.com | Active | User | 2024-01-19 |
789+ | 3 | Carol White | carol@example.com | Inactive | User | 2024-01-15 |
790+ | 4 | David Brown | david@example.com | Active | Moderator | 2024-01-21 |
791+ | 5 | Eve Wilson | eve@example.com | Active | User | 2024-01-18 |
792+ | 6 | Frank Miller | frank@example.com | Pending | User | 2024-01-10 |
793+ | 7 | Grace Lee | grace@example.com | Active | Admin | 2024-01-22 |
794+ | 8 | Henry Davis | henry@example.com | Active | User | 2024-01-17 |
795+
796+ ## Narrow Table
797+
798+ | # | Item |
799+ |----|------|
800+ | 1 | First |
801+ | 2 | Second |
802+ | 3 | Third |
803+
804+ ## Wide Table with Long Content
805+
806+ | Configuration Key | Default Value | Description | Environment Variable |
807+ |-------------------|---------------|-------------|---------------------|
808+ | \`api.timeout\` | 30000 | Request timeout in milliseconds | \`API_TIMEOUT\` |
809+ | \`cache.enabled\` | true | Enable response caching | \`CACHE_ENABLED\` |
810+ | \`logging.level\` | info | Log verbosity level (debug, info, warn, error) | \`LOG_LEVEL\` |
811+ | \`server.port\` | 3000 | Port number for HTTP server | \`PORT\` |
812+
813+ These tables should render cleanly without any disruptive copy or download actions.` ,
814+ } ,
815+ ] ,
816+ metadata : {
817+ historySequence : 2 ,
818+ timestamp : STABLE_TIMESTAMP + 1000 ,
819+ model : "claude-sonnet-4-20250514" ,
820+ usage : {
821+ inputTokens : 100 ,
822+ outputTokens : 500 ,
823+ totalTokens : 600 ,
824+ } ,
825+ duration : 2000 ,
826+ } ,
827+ } ) ;
828+
829+ // Mark as caught up
830+ callback ( { type : "caught-up" } ) ;
831+ } , 100 ) ;
832+
833+ return ( ) => {
834+ // Cleanup
835+ } ;
836+ } ,
837+ onMetadata : ( ) => ( ) => undefined ,
838+ sendMessage : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
839+ resumeStream : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
840+ interruptStream : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
841+ truncateHistory : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
842+ replaceChatHistory : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
843+ getInfo : ( ) => Promise . resolve ( null ) ,
844+ executeBash : ( ) =>
845+ Promise . resolve ( {
846+ success : true ,
847+ data : { success : true , output : "" , exitCode : 0 , wall_duration_ms : 0 } ,
848+ } ) ,
849+ } ,
850+ } ,
851+ } ) ;
852+
853+ // Set initial workspace selection
854+ localStorage . setItem (
855+ "selectedWorkspace" ,
856+ JSON . stringify ( {
857+ workspaceId : workspaceId ,
858+ projectPath : "/home/user/projects/my-app" ,
859+ projectName : "my-app" ,
860+ namedWorkspacePath : "/home/user/.cmux/src/my-app/feature" ,
861+ } )
862+ ) ;
863+
864+ initialized . current = true ;
865+ }
866+
867+ return < AppLoader /> ;
868+ } ;
869+
870+ return < AppWithTableMocks /> ;
871+ } ,
872+ } ;
0 commit comments