fix: mobile UI bugs and documentation improvements

- docs: add clarifying comment for AI SDK provider imports on /docs and /sdk pages
- fix: remove duplicate footer on /docs page (was rendered in both page and root layout)
- fix: prevent "Official" badge from overflowing on mobile tool cards (/home)
- fix: center Specification/Full Example toggle buttons on mobile (/spec)
- fix: ecosystem diagram layout on mobile - increase height and reposition legend (/integrations)
This commit is contained in:
Ajax Davis 2026-01-07 20:56:56 +10:00
parent 114798ce9e
commit b8b4e44b50
5 changed files with 66 additions and 37 deletions

View file

@ -6,7 +6,6 @@ import { Button } from '@tpmjs/ui/Button/Button';
import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock';
import Link from 'next/link';
import { useEffect, useState } from 'react';
import { AppFooter } from '~/components/AppFooter';
import { AppHeader } from '~/components/AppHeader';
const NAV_SECTIONS = [
@ -351,6 +350,7 @@ export default function DocsPage(): React.ReactElement {
<CodeBlock
language="typescript"
code={`import { streamText } from 'ai';
// Import AI SDK provider for your selected model (OpenAI, Anthropic, Google, etc.)
import { anthropic } from '@ai-sdk/anthropic';
import { registrySearchTool } from '@tpmjs/registry-search';
import { registryExecuteTool } from '@tpmjs/registry-execute';
@ -591,6 +591,8 @@ export const registryExecute = tool({
<CodeBlock
language="typescript"
code={`import { streamText } from 'ai';
// Import AI SDK provider for your selected model (OpenAI, Anthropic, Google, etc.)
import { anthropic } from '@ai-sdk/anthropic';
import { registrySearchTool } from '@tpmjs/registry-search';
import { registryExecute } from './tools'; // Your wrapped version
@ -1443,7 +1445,6 @@ export TPMJS_EXECUTOR_URL=https://executor.mycompany.com`}
</div>
</main>
</div>
<AppFooter />
</div>
);
}

View file

@ -104,15 +104,15 @@ export default async function HomePage(): Promise<React.ReactElement> {
className="group"
>
<div className="p-6 border border-border rounded-lg bg-surface hover:border-foreground transition-colors h-full flex flex-col">
<div className="flex items-start justify-between mb-3">
<h3 className="text-lg font-semibold text-foreground group-hover:text-brutalist-accent transition-colors">
<div className="flex items-start justify-between gap-2 mb-3">
<h3 className="text-lg font-semibold text-foreground group-hover:text-brutalist-accent transition-colors min-w-0 break-words">
{tool.package.npmPackageName}
<span className="text-xs text-foreground-tertiary ml-2">
({tool.name})
</span>
</h3>
{tool.package.isOfficial && (
<Badge variant="default" size="sm">
<Badge variant="default" size="sm" className="flex-shrink-0">
Official
</Badge>
)}

View file

@ -117,6 +117,7 @@ export default function SDKPage(): React.ReactElement {
<CodeBlock
language="typescript"
code={`import { streamText } from 'ai';
// Import AI SDK provider for your selected model (OpenAI, Anthropic, Google, etc.)
import { anthropic } from '@ai-sdk/anthropic';
import { registrySearchTool } from '@tpmjs/registry-search';
import { registryExecuteTool } from '@tpmjs/registry-execute';
@ -518,6 +519,7 @@ export const registryExecute = tool({
<CodeBlock
language="typescript"
code={`import { streamText } from 'ai';
// Import AI SDK provider for your selected model (OpenAI, Anthropic, Google, etc.)
import { anthropic } from '@ai-sdk/anthropic';
import { registrySearchTool } from '@tpmjs/registry-search';
import { registryExecute } from './tools'; // Your wrapped version

View file

@ -121,7 +121,7 @@ export default function SpecPage(): React.ReactElement {
</div>
{/* View Toggle */}
<div className="flex gap-1 p-1 bg-surface rounded-lg border border-border">
<div className="flex gap-1 p-1 bg-surface rounded-lg border border-border self-center md:self-auto">
<button
type="button"
onClick={() => setView('spec')}

View file

@ -76,7 +76,9 @@ export function EcosystemDiagram(): React.ReactElement {
const updateDimensions = () => {
if (containerRef.current) {
const width = containerRef.current.clientWidth;
const height = Math.min(500, Math.max(400, width * 0.5));
const isMobile = width < 600;
// Mobile needs more height for vertical layout (5 nodes stacked)
const height = isMobile ? 480 : Math.min(500, Math.max(400, width * 0.5));
setDimensions({ width, height });
}
};
@ -414,47 +416,71 @@ export function EcosystemDiagram(): React.ReactElement {
});
});
// Add legend
const legend = svg.append('g').attr('transform', `translate(${width - 160}, 20)`);
// Add legend - positioned differently for mobile vs desktop
const legendItems = [
{ label: 'BlocksAI', color: colors.blocksai },
{ label: 'TPMJS', color: colors.tpmjs },
{ label: 'HLLM', color: colors.hllm },
];
legendItems.forEach((item, i) => {
const y = i * 22;
legend
.append('rect')
.attr('x', 0)
.attr('y', y)
.attr('width', 14)
.attr('height', 14)
.attr('rx', 3)
.attr('fill', item.color.fill)
.attr('stroke', item.color.stroke)
.attr('stroke-width', 1.5);
if (isMobile) {
// Mobile: horizontal legend at bottom
const legend = svg
.append('g')
.attr('transform', `translate(${width / 2 - 100}, ${height - 30})`);
legend
.append('text')
.attr('x', 20)
.attr('y', y + 11)
.attr('font-size', '11px')
.attr('fill', '#64748b')
.text(item.label);
});
legendItems.forEach((item, i) => {
const x = i * 70;
legend
.append('rect')
.attr('x', x)
.attr('y', 0)
.attr('width', 12)
.attr('height', 12)
.attr('rx', 3)
.attr('fill', item.color.fill)
.attr('stroke', item.color.stroke)
.attr('stroke-width', 1.5);
legend
.append('text')
.attr('x', x + 16)
.attr('y', 10)
.attr('font-size', '10px')
.attr('fill', '#64748b')
.text(item.label);
});
} else {
// Desktop: vertical legend at top right
const legend = svg.append('g').attr('transform', `translate(${width - 160}, 20)`);
legendItems.forEach((item, i) => {
const y = i * 22;
legend
.append('rect')
.attr('x', 0)
.attr('y', y)
.attr('width', 14)
.attr('height', 14)
.attr('rx', 3)
.attr('fill', item.color.fill)
.attr('stroke', item.color.stroke)
.attr('stroke-width', 1.5);
legend
.append('text')
.attr('x', 20)
.attr('y', y + 11)
.attr('font-size', '11px')
.attr('fill', '#64748b')
.text(item.label);
});
}
}, [dimensions]);
return (
<div ref={containerRef} className="relative w-full">
<svg
ref={svgRef}
width={dimensions.width}
height={dimensions.height}
className="w-full"
style={{ minHeight: '400px' }}
/>
<svg ref={svgRef} width={dimensions.width} height={dimensions.height} className="w-full" />
{/* Tooltip */}
{tooltip.visible && (