Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .ai/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@
- Test framework: Vitest with React Testing Library
- Coverage reporting: V8 coverage provider

### Testing Requirements

- **New functionality must include tests** - Never implement new features without corresponding tests
- **Update existing tests** when modifying functionality
- **Test all edge cases** - especially for utility functions and complex logic
- **Component tests** should cover props, state changes, and user interactions
- **Utility tests** should cover all branches and error conditions
- **Integration tests** for command system and terminal interactions
- **Mock external dependencies** appropriately in tests
- **Test file organization** - keep tests in `__tests__/` directories alongside components

## Quality Assurance

- Linting: `pnpm lint` (Biome)
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# nipsys.dev
# nipsys.eth site

```
$ whoami
Expand All @@ -13,7 +13,7 @@ built this when i got tired of the usual portfolio sites. figured if i'm gonna s

licensed under GPL-3.0 because that's how it should be.

**status**: work in progress. not even hosted yet, but getting there.
**status**: work in progress. live on IPFS via IPNS `k2k4r8ng8uzrtqb5ham8kao889m8qezu96z4w3lpinyqghum43veb6n3`

## why a terminal?

Expand All @@ -28,11 +28,10 @@ licensed under GPL-3.0 because that's how it should be.
- ✅ commands and navigation
- ✅ responsive design
- ✅ internationalization
- ✅ deployed on IPFS
- 🚧 content still being written
- 🚧 ipfs deployment setup
- 🚧 automatic ipfs deployment setup
Comment thread
nipsysdev marked this conversation as resolved.
- 🚧 figuring out codex hosting
- 🚧 web3 integration features
- ❌ not live yet (soon™)

## commands

Expand Down
2 changes: 2 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import createNextIntlPlugin from 'next-intl/plugin';

const nextConfig: NextConfig = {
output: 'export',
trailingSlash: true,
images: { unoptimized: true },
};

const withNextIntl = createNextIntlPlugin();
Expand Down
42 changes: 41 additions & 1 deletion src/components/terminal/TerminalPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,55 @@ interface State {
}

export default class TerminalPrompt extends Component<Props, State> {
// Constants for IPFS host display formatting
private static readonly MAX_HOST_LENGTH = 25;
private static readonly CID_MIN_LENGTH = 16;
private static readonly CID_TRUNCATION_LENGTH = 8;

state: State = {
inputText: '',
inputRef: createRef<HTMLInputElement>(),
autocompleteRef: createRef<HTMLDivElement>(),
historyIdx: -1,
autocomplete: null,
host: window.location.host.split(':')[0],
host: this.getDisplayHost(),
};

private getDisplayHost(): string {
Comment thread
nipsysdev marked this conversation as resolved.
const fullHost = window.location.host.split(':')[0];

// Check if it's an IPFS host (contains ipns or ipfs and is long)
if (fullHost.includes('ipns') || fullHost.includes('ipfs')) {
Comment thread
nipsysdev marked this conversation as resolved.
if (fullHost.length > TerminalPrompt.MAX_HOST_LENGTH) {
// For IPFS, show first CID_TRUNCATION_LENGTH chars + ... + last CID_TRUNCATION_LENGTH chars of the CID + domain
const parts = fullHost.split('.');
const cidPart = parts[0]; // The long hash part

// Validate that we have domain parts before constructing domainPart
let domainPart = '';
if (parts.length > 1) {
domainPart = parts.slice(1).join('.'); // Everything after the CID
} else {
// Fallback to fullHost if no '.' is present (edge case)
return fullHost;
}

if (cidPart.length > TerminalPrompt.CID_MIN_LENGTH) {
const prefix = cidPart.substring(
0,
TerminalPrompt.CID_TRUNCATION_LENGTH,
);
const suffix = cidPart.substring(
cidPart.length - TerminalPrompt.CID_TRUNCATION_LENGTH,
);
return `${prefix}...${suffix}.${domainPart}`;
}
}
}

return fullHost;
}

componentDidMount(): void {
if (this.props.entry) {
this.setInput(getTerminalEntryInput(this.props.entry));
Expand Down
99 changes: 98 additions & 1 deletion src/components/terminal/__tests__/TerminalPrompt.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,14 @@ describe('TerminalPrompt', () => {
it('renders Terminal prompt span', () => {
render(<TerminalPrompt {...defaultProps} />);

const promptSpan = screen.getByText('translated_visitor@localhost:~$');
// Check for the terminal prompt span with a more flexible matcher
const promptSpan = screen.getByText((content, element) => {
return (
element?.tagName.toLowerCase() === 'span' &&
content.includes('translated_visitor') &&
content.includes(':~$')
);
});
expect(promptSpan).toBeInTheDocument();
});

Expand Down Expand Up @@ -836,4 +843,94 @@ describe('TerminalPrompt', () => {
expect(mockSetLastKeyDown).toHaveBeenCalledTimes(2);
});
});

describe('getDisplayHost', () => {
it('returns full host for regular domains', () => {
Object.defineProperty(window, 'location', {
value: { host: 'example.com' },
writable: true,
});

render(<TerminalPrompt {...defaultProps} />);
expect(
screen.getByText('translated_visitor@example.com:~$'),
).toBeInTheDocument();
});

it('returns full host for short IPFS hosts', () => {
Object.defineProperty(window, 'location', {
value: { host: 'short.ipfs.dweb.link' },
writable: true,
});

render(<TerminalPrompt {...defaultProps} />);
expect(
screen.getByText('translated_visitor@short.ipfs.dweb.link:~$'),
).toBeInTheDocument();
});

it('shortens long IPNS hosts correctly', () => {
Object.defineProperty(window, 'location', {
value: {
host: 'k2k4r8ng8uzrtqb5ham8kao889m8qezu96z4w3lpinyqghum43veb6n3.ipns.dweb.link',
},
writable: true,
});

render(<TerminalPrompt {...defaultProps} />);

// Check for the shortened IPNS host in the prompt span
const promptSpan = screen.getByText((content, element) => {
return (
element?.tagName.toLowerCase() === 'span' &&
content.includes('k2k4r8ng...43veb6n3.ipns.dweb.link')
);
});
expect(promptSpan).toBeInTheDocument();
});

it('shortens long IPFS hosts correctly', () => {
Object.defineProperty(window, 'location', {
value: {
host: 'QmYjtig7VJQ6XsnUjqqJvj7QaMcCAwtrgNdahSiFofrE7o.ipfs.dweb.link',
},
writable: true,
});

render(<TerminalPrompt {...defaultProps} />);

// Check for the shortened IPFS host in the prompt span
const promptSpan = screen.getByText((content, element) => {
return (
element?.tagName.toLowerCase() === 'span' &&
content.includes('QmYjtig7...iFofrE7o.ipfs.dweb.link')
);
});
expect(promptSpan).toBeInTheDocument();
});

it('handles hosts with port numbers', () => {
Object.defineProperty(window, 'location', {
value: { host: 'localhost:3000' },
writable: true,
});

render(<TerminalPrompt {...defaultProps} />);
expect(
screen.getByText('translated_visitor@localhost:~$'),
).toBeInTheDocument();
});

it('handles CID shorter than 16 characters', () => {
Object.defineProperty(window, 'location', {
value: { host: 'shortcid.ipfs.dweb.link' },
writable: true,
});

render(<TerminalPrompt {...defaultProps} />);
expect(
screen.getByText('translated_visitor@shortcid.ipfs.dweb.link:~$'),
).toBeInTheDocument();
});
});
});