Skip to content

Commit 7d2bcee

Browse files
V48 (specification-implementation): Modularize marketing, docs, and conversations experiences
Split large experience shells into named co-located units per SOURCE_LAYOUT: - Marketing: data/helpers + subcomponents for screenshot, competitor, walkthrough, marketplace, BTD investment, and acceleration sections; fix sibling import paths - Docs: bitcode-docs-content → types/helpers/content modules; thin DocsArticlePage into rail/card/specimen/API/manual components - Conversations: overlay types/hydration/side panels; rich-text token helpers; factory edge-case split; edge-case facade + concern modules Update experience READMEs and FAMILIARIZATION.md §§7.1, 7.5, 7.6.
1 parent 5acebc4 commit 7d2bcee

96 files changed

Lines changed: 5932 additions & 5305 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.

FAMILIARIZATION.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,9 @@ Named entry file — **not** `index.tsx`. Top-of-file overview comment on non-tr
372372
### 7.1 Marketing (`/`)
373373

374374
Landing, walkthrough, marketplace narrative, competitor tables, BTD education.
375-
Home: `uapi/components/marketing/`. Large section components; prefer SRP splits
376-
into sub-units when editing.
375+
Home: `uapi/components/marketing/`. Large sections are modularized (data/helpers
376+
+ co-located subcomponents under each `Marketing*Section/` directory); see
377+
`uapi/components/marketing/README.md`.
377378

378379
### 7.2 Deposits (`/deposits`)
379380

@@ -432,14 +433,17 @@ App shell: `uapi/app/packs/` re-exports the page client. Layout contract:
432433

433434
### 7.5 Docs (`/docs`)
434435

435-
Public documentation articles. Content largely in `docs/models/bitcode-docs-content.ts`
436-
+ `DocsArticlePage`.
436+
Public documentation articles. Content is modular under
437+
`docs/models/` (`bitcode-docs-types`, `bitcode-docs-helpers`, `content/*`) with
438+
stable re-export `bitcode-docs-content.ts`. UI: thin `DocsArticlePage` plus
439+
rail/card/specimen/API/manual subcomponents.
437440

438441
### 7.6 Conversations
439442

440443
Full commercial Conversations UX is **deferred post-V48**, but structure remains
441-
under `uapi/components/conversations/` (overlay, waterfall, source selectors,
442-
utilities). Prefer `/packs` as post-auth landing, not Terminal.
444+
under `uapi/components/conversations/`. Overlay is a shell + side panels +
445+
hydration hook; rich-text input has co-located token helpers; utilities split
446+
factory vs edge-case concerns. Prefer `/packs` as post-auth landing, not Terminal.
443447

444448
### 7.7 Auxillaries
445449

uapi/components/conversations/ConversationsEnhancedRichTextInput/ConversationsEnhancedRichTextInput.tsx

Lines changed: 15 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
'use client';
22

3+
/**
4+
* Enhanced rich-text conversation input with token pickers.
5+
* Token types and display helpers are co-located modules.
6+
*/
7+
38
import React, { useState, useRef, useEffect, useCallback } from 'react';
49
// motion and AnimatePresence not needed here
510
// import { motion, AnimatePresence } from 'framer-motion';
@@ -10,38 +15,15 @@ import PipelineRunPicker from '@/components/conversations/pickers/PipelineRunPic
1015
import '@/styles/conversations/rich-text-input.css';
1116
import glassyInputStyles from '@/components/bitcode/inputs/GlassyInput/GlassyInput/glassy-input.module.css';
1217

13-
interface Token {
14-
id: string;
15-
type: 'evidence_document' | 'shippable' | 'attachment' | 'source' | 'destination' | 'pipeline_run' | 'command';
16-
text: string;
17-
data: any;
18-
displayInfo?: string;
19-
}
20-
21-
interface RichTextInputProps {
22-
onSend: (message: string, tokens: Token[]) => void;
23-
placeholder?: string;
24-
disabled?: boolean;
25-
/**
26-
* Enable Conversations pickers (^ @ + # !) – set to false for plain input mode
27-
*/
28-
enablePickers?: boolean;
29-
className?: string;
30-
/**
31-
* When true, the textarea stretches to fill the height of its container so
32-
* callers can align it perfectly inside fixed-height flex rows.
33-
*/
34-
fullHeight?: boolean;
35-
/**
36-
* Use minimal vertical padding so the overall height matches button-like
37-
* controls. Useful for compact instruction bars.
38-
*/
39-
compact?: boolean;
40-
/**
41-
* Current conversation ID for OTF target picker defaults
42-
*/
43-
currentConversationId?: string;
44-
}
18+
import type {
19+
ConversationsRichTextToken as Token,
20+
ConversationsEnhancedRichTextInputProps as RichTextInputProps,
21+
} from './conversations-enhanced-rich-text-input.types';
22+
import {
23+
getTokenIcon,
24+
getTokenTypeLabel,
25+
adjustTokenSpacing as adjustTokenSpacingHelper,
26+
} from './conversations-enhanced-rich-text-helpers';
4527

4628
export default function RichTextInput({
4729
onSend,
@@ -505,71 +487,7 @@ export default function RichTextInput({
505487
}
506488
}, [text, tokens, renderRichText]);
507489

508-
// Helper function to get the appropriate icon for each token type
509-
const getTokenIcon = (type: string) => {
510-
switch (type) {
511-
case 'evidence_document':
512-
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 16v-3a2 2 0 0 0-2-2h-4V7a2 2 0 0 0-2-2H6"></path><path d="M18 14v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4"></path><path d="M6 5l4 4-4 4"></path></svg>';
513-
case 'shippable':
514-
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><path d="M22 4L12 14.01l-3-3"></path></svg>';
515-
case 'attachment':
516-
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>';
517-
case 'source':
518-
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>';
519-
case 'command':
520-
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"></path></svg>';
521-
case 'destination':
522-
case 'pipeline_run':
523-
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path></svg>';
524-
default:
525-
return '';
526-
}
527-
};
528-
529-
// Helper function to get a human-readable label for each token type
530-
const getTokenTypeLabel = (type: string) => {
531-
switch (type) {
532-
case 'evidence_document':
533-
return 'Evidence Document';
534-
case 'shippable':
535-
return 'Shippable';
536-
case 'attachment':
537-
return 'Attachment';
538-
case 'source':
539-
return 'Connect source';
540-
case 'command':
541-
return 'Command';
542-
case 'destination':
543-
case 'pipeline_run':
544-
return 'Output destination';
545-
default:
546-
return type.charAt(0).toUpperCase() + type.slice(1);
547-
}
548-
};
549-
550-
// Helper function to ensure proper spacing around tokens
551-
const adjustTokenSpacing = (inputText: string) => {
552-
if (!tokens.length) return inputText;
553-
554-
let adjustedText = inputText;
555-
556-
// For each token, ensure it has proper spacing
557-
tokens.forEach(token => {
558-
// Find all instances of the token without proper spacing
559-
const tokenText = token.text.trim();
560-
const escapedText = tokenText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
561-
562-
// Check for tokens without space before
563-
const noSpaceBeforeRegex = new RegExp(`([^\\s])${escapedText}`, 'g');
564-
adjustedText = adjustedText.replace(noSpaceBeforeRegex, `$1 ${tokenText}`);
565-
566-
// Check for tokens without space after (unless at end of text)
567-
const noSpaceAfterRegex = new RegExp(`${escapedText}([^\\s])`, 'g');
568-
adjustedText = adjustedText.replace(noSpaceAfterRegex, `${tokenText} $1`);
569-
});
570-
571-
return adjustedText;
572-
};
490+
const adjustTokenSpacing = (inputText: string) => adjustTokenSpacingHelper(inputText, tokens);
573491
// Prevent default drop behavior; optional file handling
574492
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
575493
e.preventDefault();
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Token display helpers for Conversations enhanced rich-text input.
3+
*/
4+
import type { ConversationsRichTextToken } from './conversations-enhanced-rich-text-input.types';
5+
6+
export function getTokenIcon(type: string): string {
7+
switch (type) {
8+
case 'evidence_document':
9+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 16v-3a2 2 0 0 0-2-2h-4V7a2 2 0 0 0-2-2H6"></path><path d="M18 14v4a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4"></path><path d="M6 5l4 4-4 4"></path></svg>';
10+
case 'shippable':
11+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><path d="M22 4L12 14.01l-3-3"></path></svg>';
12+
case 'attachment':
13+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>';
14+
case 'source':
15+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path></svg>';
16+
case 'command':
17+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"></path></svg>';
18+
case 'destination':
19+
case 'pipeline_run':
20+
return '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"></path></svg>';
21+
default:
22+
return '';
23+
}
24+
}
25+
26+
export function getTokenTypeLabel(type: string): string {
27+
switch (type) {
28+
case 'evidence_document':
29+
return 'Evidence Document';
30+
case 'shippable':
31+
return 'Shippable';
32+
case 'attachment':
33+
return 'Attachment';
34+
case 'source':
35+
return 'Connect source';
36+
case 'command':
37+
return 'Command';
38+
case 'destination':
39+
case 'pipeline_run':
40+
return 'Output destination';
41+
default:
42+
return type.charAt(0).toUpperCase() + type.slice(1);
43+
}
44+
}
45+
46+
export function adjustTokenSpacing(
47+
inputText: string,
48+
tokens: readonly ConversationsRichTextToken[],
49+
): string {
50+
if (!tokens.length) return inputText;
51+
52+
let adjustedText = inputText;
53+
54+
tokens.forEach((token) => {
55+
const tokenText = token.text.trim();
56+
const escapedText = tokenText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
57+
58+
const noSpaceBeforeRegex = new RegExp(`([^\\s])${escapedText}`, 'g');
59+
adjustedText = adjustedText.replace(noSpaceBeforeRegex, `$1 ${tokenText}`);
60+
61+
const noSpaceAfterRegex = new RegExp(`${escapedText}([^\\s])`, 'g');
62+
adjustedText = adjustedText.replace(noSpaceAfterRegex, `${tokenText} $1`);
63+
});
64+
65+
return adjustedText;
66+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Token model for Conversations enhanced rich-text input.
3+
*/
4+
export interface ConversationsRichTextToken {
5+
id: string;
6+
type: 'evidence_document' | 'shippable' | 'attachment' | 'source' | 'destination' | 'pipeline_run' | 'command';
7+
text: string;
8+
data: any;
9+
displayInfo?: string;
10+
}
11+
12+
export interface ConversationsEnhancedRichTextInputProps {
13+
onSend: (message: string, tokens: ConversationsRichTextToken[]) => void;
14+
placeholder?: string;
15+
disabled?: boolean;
16+
/**
17+
* Enable Conversations pickers (^ @ + # !) – set to false for plain input mode
18+
*/
19+
enablePickers?: boolean;
20+
className?: string;
21+
/**
22+
* When true, the textarea stretches to fill the height of its container so
23+
* callers can align it perfectly inside fixed-height flex rows.
24+
*/
25+
fullHeight?: boolean;
26+
/**
27+
* Use minimal vertical padding so the overall height matches button-like
28+
* controls. Useful for compact instruction bars.
29+
*/
30+
compact?: boolean;
31+
/**
32+
* Current conversation ID for OTF target picker defaults
33+
*/
34+
currentConversationId?: string;
35+
}

0 commit comments

Comments
 (0)