Skip to main content
Yasumu’s frontend is built with Next.js 16 and React 19, providing a fast, responsive user interface. Despite being a desktop app, Next.js was chosen for its excellent developer experience and rich ecosystem.

Technology foundation

Core framework

  • Next.js 16.1.1+: React framework with App Router
  • React 19.2.1+: UI library with experimental compiler
  • TypeScript 5.9.3+: Type-safe development
  • Tailwind CSS 4.x: Utility-first styling

Key decision: Static export mode

Yasumu uses Next.js in static export mode, meaning:
// next.config.ts
export default {
  output: 'export',  // No server-side rendering
  images: {
    unoptimized: true // No image optimization API needed
  }
};
Why?
  • Desktop apps don’t need SSR
  • Simpler deployment (just static files)
  • Faster startup (no Next.js server)
  • All routes are client-side

Project structure

The frontend lives in apps/yasumu/:
app/
├── src/
│   ├── app/                 # Next.js App Router pages
│   │   ├── layout.tsx       # Root layout
│   │   ├── page.tsx         # Home page
│   │   └── [workspace]/     # Workspace routes
│   ├── components/          # React components
│   │   ├── ui/              # shadcn/ui components
│   │   ├── rest/            # REST client components
│   │   └── workspace/       # Workspace components
│   ├── lib/                 # Utilities and helpers
│   │   ├── yasumu.ts        # Yasumu SDK instance
│   │   ├── rpc.ts           # RPC client setup
│   │   └── utils.ts         # Shared utilities
│   ├── stores/              # Zustand state stores
│   ├── hooks/               # Custom React hooks
│   └── styles/              # Global styles
├── public/                  # Static assets
└── src-tauri/               # Tauri Rust code

State management

Yasumu uses a hybrid state management approach:

Server state: TanStack Query

For data fetched from the RPC layer:
import { useQuery } from '@tanstack/react-query';
import { yasumu } from '@/lib/yasumu';

function WorkspaceList() {
  const { data: workspaces } = useQuery({
    queryKey: ['workspaces'],
    queryFn: async () => {
      const result = await yasumu.rpc.workspace.list.$query();
      return result.data;
    }
  });
  
  return <div>{/* render workspaces */}</div>;
}
Benefits:
  • Automatic caching
  • Background refetching
  • Optimistic updates
  • Loading/error states

Client state: Zustand

For UI-only state:
import { create } from 'zustand';

interface EditorStore {
  activeTab: string | null;
  setActiveTab: (tab: string) => void;
}

export const useEditorStore = create<EditorStore>((set) => ({
  activeTab: null,
  setActiveTab: (tab) => set({ activeTab: tab })
}));
Use cases:
  • Active tab/panel state
  • Modal open/close state
  • Theme preferences
  • UI preferences (sidebar collapsed, etc.)

URL state: nuqs

For state that should persist in the URL:
import { useQueryState } from 'nuqs';

function RestClient() {
  const [method, setMethod] = useQueryState('method', {
    defaultValue: 'GET'
  });
  
  return <div>{/* UI with method selector */}</div>;
}
Benefits:
  • Shareable URLs
  • Browser back/forward navigation
  • State persists across refreshes

RPC integration

The frontend communicates with Tanxium via a type-safe RPC layer:

Setting up the client

// lib/yasumu.ts
import { Yasumu } from '@yasumu/core';
import { invoke } from '@tauri-apps/api/core';

const platformBridge = {
  invoke: async (context, command) => {
    // Get RPC server port from Tauri
    const port = await invoke<number>('get_rpc_port');
    
    // Make HTTP request to Tanxium
    const response = await fetch(`http://localhost:${port}/rpc`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(command)
    });
    
    return response.json();
  }
};

export const yasumu = new Yasumu({ platformBridge });

Making RPC calls

// Execute a REST request
const result = await yasumu.rpc.rest.execute.$mutate({
  parameters: [{ 
    url: 'https://api.example.com',
    method: 'GET'
  }]
});

// List workspaces
const workspaces = await yasumu.rpc.workspace.list.$query();

// Create entity
const entity = await yasumu.rpc.entity.create.$mutate({
  parameters: [{ name: 'New Request', type: 'rest' }]
});
The RPC layer provides:
  • Full TypeScript type safety
  • Auto-completion in IDEs
  • Compile-time error checking
  • Runtime validation

Component architecture

Design system: shadcn/ui

Yasumu uses shadcn/ui for base components:
# Adding components
npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add form
Components live in src/components/ui/ and can be customized:
// components/ui/button.tsx
import { cva } from 'class-variance-authority';

const buttonVariants = cva(
  'rounded-md font-medium transition-colors',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground',
        destructive: 'bg-destructive text-destructive-foreground',
        outline: 'border border-input bg-background'
      }
    }
  }
);

Component patterns

Yasumu follows these patterns:

1. Feature-based organization

components/
├── rest/
│   ├── RestClient.tsx        # Main REST client
│   ├── RequestEditor.tsx     # Request editing
│   ├── ResponseViewer.tsx    # Response display
│   └── HistoryPanel.tsx      # Request history
├── workspace/
│   ├── WorkspaceSidebar.tsx
│   ├── EntityTree.tsx
│   └── EntityCard.tsx
└── ui/                        # Base components

2. Compound components

function RestClient() {
  return (
    <RestClient.Root>
      <RestClient.Sidebar />
      <RestClient.Editor />
      <RestClient.Response />
    </RestClient.Root>
  );
}

RestClient.Root = ({ children }) => <div>{children}</div>;
RestClient.Sidebar = () => <div>Sidebar</div>;
// ...

3. Custom hooks for logic

// hooks/use-rest-request.ts
export function useRestRequest(entityId: string) {
  const [loading, setLoading] = useState(false);
  
  const execute = useCallback(async () => {
    setLoading(true);
    try {
      const result = await yasumu.rpc.rest.execute.$mutate({
        parameters: [{ entityId }]
      });
      return result;
    } finally {
      setLoading(false);
    }
  }, [entityId]);
  
  return { execute, loading };
}

Monaco Editor integration

Yasumu uses Monaco Editor for code editing:
import Editor from '@monaco-editor/react';

function CodeEditor({ value, onChange, language }) {
  return (
    <Editor
      height="400px"
      language={language}
      value={value}
      onChange={onChange}
      theme="vs-dark"
      options={{
        minimap: { enabled: false },
        fontSize: 14,
        lineNumbers: 'on',
        scrollBeyondLastLine: false
      }}
    />
  );
}

Custom languages

For .ysl files, Yasumu registers a custom Monaco language:
import * as monaco from 'monaco-editor';

monaco.languages.register({ id: 'ysl' });
monaco.languages.setMonarchTokensProvider('ysl', {
  tokenizer: {
    root: [
      [/@[a-zA-Z_]\w*/, 'variable'],
      [/"[^"]*"/, 'string'],
      // ... more rules
    ]
  }
});

Styling approach

Tailwind CSS

Most styling uses Tailwind utilities:
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
  Click me
</button>

CSS variables for themes

/* styles/globals.css */
:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 221.2 83.2% 53.3%;
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  --primary: 217.2 91.2% 59.8%;
}

Theme switching

import { useTheme } from 'next-themes';

function ThemeToggle() {
  const { theme, setTheme } = useTheme();
  
  return (
    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
      Toggle theme
    </button>
  );
}

Performance optimizations

React Compiler

Yasumu uses the experimental React Compiler:
// next.config.ts
export default {
  experimental: {
    reactCompiler: true
  }
};
This automatically optimizes components, eliminating the need for useMemo and useCallback in most cases.

Code splitting

Dynamic imports for heavy components:
import dynamic from 'next/dynamic';

const MonacoEditor = dynamic(
  () => import('@monaco-editor/react'),
  { ssr: false, loading: () => <div>Loading editor...</div> }
);

Virtualization

Large lists use virtualization:
import { useVirtualizer } from '@tanstack/react-virtual';

function EntityList({ entities }) {
  const parentRef = useRef();
  
  const virtualizer = useVirtualizer({
    count: entities.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 50
  });
  
  return (
    <div ref={parentRef}>
      {virtualizer.getVirtualItems().map(item => (
        <div key={item.key}>{entities[item.index].name}</div>
      ))}
    </div>
  );
}

Build configuration

// next.config.ts
import type { NextConfig } from 'next';

const config: NextConfig = {
  output: 'export',           // Static export
  distDir: 'out',             // Output directory
  images: {
    unoptimized: true         // No image optimization
  },
  experimental: {
    reactCompiler: true,      // React Compiler
    turbo: true               // Turbopack for dev
  },
  typescript: {
    ignoreBuildErrors: false  // Strict type checking
  }
};

export default config;

Next steps

Build docs developers (and LLMs) love