Skip to content

Commit b886d0f

Browse files
V48 (specification-implementation): Further modularize marketing and conversations god modules
Split remaining large experience shells into co-located SRP units per SOURCE_LAYOUT: Marketing: - Screenshot shell → hero gallery, floating trio, how-it-works, entrance hook - Marketplace shell → candles, ticker, order book, detail, narrative, action pad - Completion / phase poetry → data modules - BTD investment → value, coaching, projection panels Conversations: - Overlay orchestration <500 → header, main content, send + view-mode hooks - Rich-text helpers: serialize/render/display - GitHub source selector cascade → use-github-source-selection - Edge-case handler facade over real network/data/performance/validation modules Update marketing/conversations READMEs, FAMILIARIZATION §§7.1/7.6, V48 notes.
1 parent b6f8027 commit b886d0f

40 files changed

Lines changed: 4287 additions & 3550 deletions

File tree

BITCODE_SPEC_V48_NOTES.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,4 +1474,20 @@ Working groups modularized remaining commercial experiences on `version/v48`:
14741474
| Docs | bitcode-docs-content → content modules + Docs* section components |
14751475
| Conversations | Overlay/rich-text/edge-case modularization (structure retained; commercial UX deferred) |
14761476

1477+
### Further god-module modularization (Garrett, 2026-07-13 — pass 2)
1478+
1479+
Remaining large marketing and conversations shells thinned further under
1480+
SOURCE_LAYOUT:
1481+
1482+
| Unit | Direction |
1483+
| --- | --- |
1484+
| `MarketingScreenshotSection` | Hero gallery, floating trio, how-it-works, entrance hook |
1485+
| `MarketingMarketplaceSection` | Candles, ticker, order book, detail, narrative, action pad |
1486+
| `MarketingCompletionCelebration` / `MarketingPipelinePhasePoetry` | Data modules |
1487+
| `MarketingBtdInvestmentExperience` | Value / coaching / projection panels |
1488+
| `ConversationsOverlay` | Header, main content, send + view-mode hooks (orchestration <500) |
1489+
| `ConversationsEnhancedRichTextInput` | Serialize/render helpers expanded |
1490+
| `ConversationsGithubSourceSelector` | Selection cascade hook |
1491+
| Edge-case handler | Real concern modules + thin class facade |
1492+
14771493
Navigation map: `FAMILIARIZATION.md`. Layout contract: `internal-docs/BITCODE_SOURCE_LAYOUT.md`.

FAMILIARIZATION.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,9 @@ Named entry file — **not** `index.tsx`. Top-of-file overview comment on non-tr
374374
Landing, walkthrough, marketplace narrative, competitor tables, BTD education.
375375
Home: `uapi/components/marketing/`. Large sections are modularized (data/helpers
376376
+ co-located subcomponents under each `Marketing*Section/` directory); see
377-
`uapi/components/marketing/README.md`.
377+
`uapi/components/marketing/README.md`. Screenshot shell composes hero gallery /
378+
how-it-works / entrance hook; marketplace composes order book, ticker, candles,
379+
detail, narrative, and action-pad units.
378380

379381
### 7.2 Deposits (`/deposits`)
380382

@@ -441,9 +443,12 @@ rail/card/specimen/API/manual subcomponents.
441443
### 7.6 Conversations
442444

443445
Full commercial Conversations UX is **deferred post-V48**, but structure remains
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.
446+
under `uapi/components/conversations/`. Overlay orchestration is a thin shell
447+
(<500) composing header/main-content/side-panels plus send + view-mode hooks;
448+
rich-text input uses co-located serialize/render helpers; GitHub source selector
449+
cascade lives in `use-github-source-selection`; edge-case handler is a facade
450+
over network/data-integrity/performance/validation modules. Prefer `/packs` as
451+
post-auth landing, not Terminal.
447452

448453
### 7.7 Auxillaries
449454

uapi/components/conversations/ConversationsEnhancedRichTextInput/ConversationsEnhancedRichTextInput.tsx

Lines changed: 9 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import type {
2020
ConversationsEnhancedRichTextInputProps as RichTextInputProps,
2121
} from './conversations-enhanced-rich-text-input.types';
2222
import {
23-
getTokenIcon,
24-
getTokenTypeLabel,
2523
adjustTokenSpacing as adjustTokenSpacingHelper,
24+
getTokenDisplayInfo,
25+
serializeTokensForSend,
26+
renderRichTextHtml,
27+
triggerCharForTokenType,
2628
} from './conversations-enhanced-rich-text-helpers';
2729

2830
export default function RichTextInput({
@@ -217,13 +219,7 @@ export default function RichTextInput({
217219
const insertToken = (token: Token) => {
218220
if (!textareaRef.current) return;
219221

220-
const triggerChar =
221-
token.type === 'evidence_document' ? '^' :
222-
token.type === 'shippable' ? '@' :
223-
token.type === 'attachment' ? '+' :
224-
token.type === 'source' ? '#' :
225-
token.type === 'destination' ? '!' :
226-
token.type === 'command' ? ':' : '!';
222+
const triggerChar = triggerCharForTokenType(token.type);
227223

228224
// Find the last occurrence of the trigger character before cursor
229225
const lastTriggerIndex = text.substring(0, cursorPosition).lastIndexOf(triggerChar);
@@ -291,107 +287,12 @@ export default function RichTextInput({
291287
setActivePicker(null);
292288
};
293289

294-
// Helper function to get additional display information for tokens
295-
const getTokenDisplayInfo = (token: Token) => {
296-
switch (token.type) {
297-
case 'evidence_document':
298-
return token.data?.description ? token.data.description.substring(0, 30) : '';
299-
case 'shippable':
300-
return token.data?.status ? token.data.status : '';
301-
case 'attachment':
302-
return token.data?.size ? token.data.size : '';
303-
case 'source':
304-
return token.data?.provider ? `${token.data.provider}${token.data.path}` : token.data?.path || '';
305-
case 'command':
306-
return token.data?.shortcut ? token.data.shortcut : '';
307-
case 'destination':
308-
case 'pipeline_run':
309-
if (!token.data?.pipelineType) return '';
310-
if (String(token.data.pipelineType).toLowerCase().includes('measure')) return 'read-measurement';
311-
if (
312-
String(token.data.pipelineType).toLowerCase().includes('asset-pack') ||
313-
String(token.data.pipelineType).toLowerCase().includes('shippable') ||
314-
String(token.data.pipelineType).toLowerCase().includes('artifact')
315-
) {
316-
return 'branch-artifact';
317-
}
318-
return `${token.data.pipelineType}`;
319-
default:
320-
return '';
321-
}
322-
};
290+
// getTokenDisplayInfo imported from helpers
323291

324292
// Handle send message
325293
const handleSend = () => {
326294
if (!text.trim()) return;
327-
328-
// Clean up tokens before sending
329-
// This ensures we only send tokens that are actually in the text
330-
const validTokens = tokens.filter(token => text.includes(token.text));
331-
332-
const serializedTokens = validTokens.map((token) => {
333-
if (token.type === 'source') {
334-
return {
335-
...token,
336-
value: token.text.trim(),
337-
metadata: {
338-
attachment_id: token.data?.id || token.data?.repoId || token.data?.path || token.text.trim(),
339-
category: 'integration',
340-
type: token.data?.type || 'github_repo',
341-
...token.data,
342-
},
343-
};
344-
}
345-
346-
if (token.type === 'attachment') {
347-
return {
348-
...token,
349-
value: token.text.trim(),
350-
metadata: {
351-
attachment_id: token.data?.id || token.data?.path || token.text.trim(),
352-
category: token.data?.category || 'file',
353-
type: token.data?.type || 'attachment',
354-
...token.data,
355-
},
356-
};
357-
}
358-
359-
if (token.type === 'destination' || token.type === 'pipeline_run') {
360-
return {
361-
...token,
362-
type: 'destination' as const,
363-
value: token.text.trim(),
364-
metadata: {
365-
attachment_id: token.data?.pipelineId || token.data?.id || token.text.trim(),
366-
category: token.data?.category || 'integration',
367-
type: token.data?.type || 'output_destination',
368-
...token.data,
369-
},
370-
};
371-
}
372-
373-
if (token.type === 'shippable') {
374-
return {
375-
...token,
376-
type: 'shippable',
377-
value: token.text.trim(),
378-
metadata: {
379-
kind: 'shippable',
380-
asset_pack_reference: token.data?.id || null,
381-
...token.data,
382-
},
383-
};
384-
}
385-
386-
return {
387-
...token,
388-
value: token.text.trim(),
389-
metadata: {
390-
...token.data,
391-
},
392-
};
393-
});
394-
295+
const serializedTokens = serializeTokensForSend(tokens, text);
395296
onSend(text, serializedTokens as Token[]);
396297
setText('');
397298
setTokens([]);
@@ -421,64 +322,9 @@ export default function RichTextInput({
421322
fileInput.click();
422323
};
423324

424-
// In RichTextInput component, memoize the renderRichText function
425325
const renderRichText = useCallback(() => {
426-
// Always return the text, even if there are no tokens
427-
// This ensures the overlay always shows something
428-
if (!text) return '';
429-
430-
// Escape user input to prevent HTML injection
431-
let result = text
432-
.replace(/&/g, '&amp;')
433-
.replace(/</g, '&lt;')
434-
.replace(/>/g, '&gt;');
435-
436-
// If no tokens, just return the plain text
437-
if (tokens.length === 0) {
438-
return result;
439-
}
440-
441-
// Sort tokens by their position in the text (to avoid replacement conflicts)
442-
// Sort by length (longest first) to avoid replacing parts of longer tokens
443-
const sortedTokens = [...tokens].sort((a, b) => {
444-
// First sort by position
445-
const posA = result.indexOf(a.text);
446-
const posB = result.indexOf(b.text);
447-
448-
if (posA !== posB) return posA - posB;
449-
450-
// If positions are the same, sort by length (longest first)
451-
return b.text.length - a.text.length;
452-
});
453-
454-
// Process each token
455-
sortedTokens.forEach(token => {
456-
// Escape special regex characters in the token text
457-
const escapedText = token.text.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
458-
459-
// Create a regex that matches the token text precisely
460-
// Using word boundaries to ensure we match whole tokens
461-
const regex = new RegExp(`(^|\\s)(${escapedText})(?=\\s|$)`, 'g');
462-
463-
// Get the appropriate icon for the token type
464-
const iconHtml = getTokenIcon(token.type);
465-
466-
// Get the label for the token type
467-
const typeLabel = getTokenTypeLabel(token.type);
468-
469-
// Replace the token text with the styled version
470-
// Use a simpler token structure with fewer nested elements and whitespace
471-
result = result.replace(regex, (match, before, tokenText) => {
472-
// Include additional information if available
473-
const infoHtml = token.displayInfo ?
474-
`<span class="token-info">${token.displayInfo}</span>` : '';
475-
476-
return `${before}<span class="token token-${token.type}" title="${typeLabel}: ${tokenText}${token.displayInfo ? ' - ' + token.displayInfo : ''}">${iconHtml}${tokenText}${infoHtml}</span>`;
477-
});
478-
});
479-
480-
return result;
481-
}, [text, tokens, cursorPosition]);
326+
return renderRichTextHtml(text, tokens);
327+
}, [text, tokens]);
482328

483329
// Sync the rich text overlay whenever text or tokens change
484330
useEffect(() => {

0 commit comments

Comments
 (0)