🎨

Cline + Claude Sonnet 4で3日でダークモードを実装した話

に公開

概要

Clineを使ってvibe coding(?)で既存のWebアプリにダークモードを実装したのでその手順などを公開します。

背景

なぜ?

Coding Agentを使用した開発は対象のコードべースの規模によって性質が結構変わるかなという感覚があります。また、簡単なアプリを1から作る場合と、既存のアプリに手を加える場合ではAgentが扱わなくてはいけないコンテキスト量が変わってきます。

一方でプロダクション環境で行うコーディングというのは新規開発はほとんどなく、既存のコードベースに手を入れる場合が多いと思います。また既存のコードを壊さないように開発をしなければいけないためその面でも異なるかなと思います。

個人開発ではAgentを試してみても仕事で使うような規模のコードベースを扱うことがあまりないので実際仕事で使えるレベルなのかよくわかりません。

他の方の記事でも、例えばブロック崩しをフリーハンドで作れることはよくわかるのですが、多少手がかかっても大きなコードベースでどんなふうに使えばいいのか知りたいと思いました。自分が知りたいのであればきっと他の人も知りたいはず。

どうしたの?

コードベースがないならそれなりの規模のコードベースを1から作ればいいのです。どうやって作るかというとCoding Agentを使う。

ということでNostrというSNSなどに使えるプロトコルに関係するクライアントアプリを作ってそれをさらにいろいろClineをつかって変更するという形で開発を行っています。

そこで作成したのがこちらのアプリです

https://github.com/ocknamo/nosskey-sdk

https://nosskey.app

今回はNostrの説明はしないので、なんとなくSNSのアカウントを管理するアプリとそのクライアントのデモだと思っていただければいいかと思います。

アリケーションの概要

NosskeyのデモはTypeScriptとSvelteで構築されたNostrクライアントアプリケーション(SPA)です。このアプリは当初、機能実装を優先したvibe codingで開発を進め、UIの統一性やテーマ対応については考慮しませんでした。

アプリの規模としては画面が5つあり、UIコンポーネントは15個(あまりちゃんと分割していないので少ない)。
機能はパスキーを使った認証機能とアカウントの管理、タイムラインの表示、ポストの投稿。設定画面もあり、多言語の切り替えやNostrのリレー管理、アカウントの設定を行うことができます。

主な課題:

  • CSSはほぼ全てハードコード
  • UIコンポーネントの整理が未実施
  • 継ぎ足しで作ってきた非統一的なスタイル
  • デモアプリとしての位置づけだったため、デザインシステムは皆無

参考にしたリソース

ダークモード実装にあたり、Nostr Designのカラーパレットを利用させてもらいました:

Modern Dark(ダークモード)

#FFFFFF \\ Text
#FFFFFF \\ Titles
#9E7EF9 \\ Primary
#383838 \\ Secondary
#FFFFFF \\ Tetriary
#222222 \\ Borders
#111111 \\ Card
#000000 \\ Background

Purple Breeze(ライトモード)

#535465 \\ Text
#11142D \\ Titles
#6C5DD3 \\ Primary
#1B1D21 \\ Secondary
#B8DCE9 \\ Tetriary
#E4E4E4 \\ Borders
#FFFFFF \\ Card
#F6F6F8 \\ Background

初期実装方針

自分はフロントエンド領域はまあまあ経験あるもののデザインシステムの実装や運用は未経験で、特にダークモード実装で使い慣れたライブラリがあるわけでもありませんでした。そのためあまりライブラリには頼らずCSSの基本機能を活用して実装することを考えていました。
ライブラリをClineに使わせるとドツボにハマりやすいイメージがあったもこの方針にした一因です。
結果的にはこの方針は筋が良かったような気がします。

ほかは基本的には無計画でしたが、要件のイメージはなんとなく持っていました。
とはいえ設定画面はもともと存在したのでそこでダークモードとライトモードの切り替えを行い、リロードなどはしなくても即座に反映する、程度です。

モデル

2025年6月現在、最高のモデルだとおもわれるClaude Sonnet 4を使用しました。

フェーズ1: 実装開始/実際のプロンプトとClineの仕事ぶり

以降プロンプトとその結果の作業を書いていきます。ダークモード実装の詳細は今回どうでもいいので技術詳細には立ち入らないので、気になる人は後で乗せる完成形のコードスニペットかリポジトリを参照ください。

ココらへんから作業のまとめ自体をClineに書かせています。感想は手書きです。

指示1: はじめのプロンプト

ユーザープロンプト:

[カラーパレット]
<前述のModern Dark / Purple Breeze (Light Mode)>

[要件]
- 設計画面から切り替えを行うことができる
- 次回も設定が引き継がれる
- 文字は判読しやすい
- 設定変更時にリロードなしにstyleに即座に適応される

[指示]
ライトモードとダークモードの切り替えに対応したいです。全力を尽くして実装してください。新しいライブラリなどはなるべく導入しないでください。

Clineの作業内容:

  1. プロジェクト構造の分析 - 既存コンポーネントとスタイルの把握
  2. テーマシステムの設計 - CSS変数ベースの動的テーマシステム構築
  3. ストア実装 - LocalStorage連携による設定永続化
  4. メインアプリ修正 - App.svelteでのCSS変数動的設定
  5. UI作成 - テーマ設定コンポーネントの実装
  6. 国際化対応 - 日英翻訳キーの追加

感想

テーマ自体はまだですが、テーマ切り替えの設定機能はこの時点で全部作ってくれました。言語切り替え機能があるのでそこを参考にしたようです。多言語化にも対応してくれました。
CSSも編集してテーマも設定してくれたんですが、指示していないからか動作確認せずに完成を宣言しました。

指示2: ブラウザでの動作確認

ユーザープロンプト:

アプリをブラウザで動作確認してテーマが適応されているか確認してみて

Clineの作業内容:

  • 開発サーバー起動(npm run dev
  • Clineデフォルトのブラウザツールでの自動テスト実行
  • テーマ切り替え機能の動作確認
  • 問題発見: ハードコードされた色が残存していることを確認

感想

ブラウザツールが起動しましたが、不具合なのかセレクトボックスによるテーマの切り替えができませんでした。手動でやると成功するのでブラウザツールの問題かとおもわれます。そのためいったん作業を中断されて次の指示に移りました。

指示3: 問題の報告

ユーザープロンプト:

カラーパレットがそれぞれの画面に適応されていいないようです。

Clineの作業内容:

  • 各画面での色適用状況の詳細調査
  • ハードコードされた色値が残っているコンポーネントの特定
  • Timeline.svelte、ボタンコンポーネント等の修正開始

感想

この指示でApp.svelte内のハードコーディングした色に気が付き修正を行いました。他のコンポーネントもなんとなく流れでハードコードカラーを修正していきます。ただ網羅的な作業にはなってないようでした。
ブラウザツールをまた使おうとしてドツボにはまっていたので次の指示に移りました。

指示4: ダークモード強制設定

ユーザープロンプト:

ブラウザツールの問題でダークモードが選択できないようなので一時的にコードを修正してダークモードにしてください。

Clineの作業内容:

  • App.svelteのテーマ判定ロジックを一時修正
  • 強制的にダークモード適用
  • ダークモードでの各画面表示確認

感想

ここは指示が具体的なためか指示どおりに完了。

指示5: 特定コンポーネントの色確認

ユーザープロンプト:

タイムライン画面やアカウント画面ではカードの色が白いようです。またボタンの色などもテーマに沿っているかどうか確認してみてください

Clineの作業内容:

  • Timeline.svelteの.event-cardスタイル完全書き換え
  • AccountScreen.svelteの警告セクション修正
  • ボタンスタイルの統一(プライマリ・セカンダリ・無効状態)

感想

修正を行いましたが網羅的に行う感じではなく、自分で表示してみるとまだ抜けがたくさんあります。Cline的には作業完了を報告してきます。

指示6: 網羅的な色調査指示

ユーザープロンプト:

私が確認したところ画面にカラーパレットが反映されていない箇所がまだ残っているようです。一旦、アプリ内を網羅的に調査して色がハードコードされている箇所を洗い出す必要があります。それらをカラーパレットのCSS変数に置き換えてください。

Clineの作業内容:

  • 包括的調査: search_filesツールで正規表現検索実行
  • 衝撃の発見: #[0-9a-fA-F]{3,6}パターンで約150箇所のハードコード色を発見
  • TimelineScreen.svelte、各種設定コンポーネントの段階的修正

感想

かなり具体的に指示したためか正規表現での検索と置き換えを開始しました。量が多いのでかなりの時間作業しています。途中でブラウザで確認しようとしてまたドツボにはまります。

指示7: 一括置換による効率化

ユーザープロンプト:

一旦ブラウザで確認は結構です。とりあえず網羅的にハードコードされたカラーを置き換えてください。完了したら再度スクリプトで残っていないことを確認してください。

Clineの作業内容:

  • SecretCacheSettings.svelte、PostForm.svelte等の大規模修正
  • 全色変更にトランジション効果追加(transition: color 0.3s ease
  • 最終確認: 143箇所の色指定が残存していることを確認

感想

めちゃくちゃ直してくれたんですがまだまだ色が残っているねという感じ。コンテキストうウィンドウが100k超えてきたのときりがいいので一旦このセッションは打ち切りにしました。

フェーズ2: ハードコード色問題の包括的解決

ここからコンテキストを切り替えて心機一転タスクを開始させます。

指示8: 根本的解決策の要求

ユーザープロンプト:

'examples/svelte-app/src/components/settings/theme-settings.svelte' ではアプリのテーマ切り替えを行っていますが、一部に ハードコードされた色が残っているコンポーネントがあり、スタイルが崩れています。対応方法を考えてください。

Clineの対応:

  • 問題の影響範囲を特定(theme-settings.svelte自体は問題なし)
  • 他コンポーネントのハードコード色調査を開始

感想

なにも知らない前提でかるく調査と計画を立案させてみます。
なぜかわかりませんがrgbaしかないと勘違いしてきたので次で軌道修正します。

指示9: 包括的調査の継続

ユーザープロンプト:

まだ調査を続けましょう。他のコンポーネントにHEX形式で色がハードコードされていませんか?

Clineの包括的調査:

  • 全件検索実行: search_files#[0-9a-fA-F]{3,6}パターン検索
  • RGBA値調査: rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)パターン検索
  • 重大性の認識: 143箇所という想定以上の深刻な問題を発見

感想

ここで状を正しく認識しました。「包括的な解決策を提案してください」「動作確認は省略」など追加プロンプトもありましたが省略。
はじめのカラーパレット以外の補助的な色も追加した包括的なテーマシステムを提案してきたので良さそうな感じがします。

指示10: 自動化による効率化

ユーザープロンプト:

コンポーネントの修正はスクリプトによる一括置換を検討してください

Clineの自動化戦略:
包括的な一括置換スクリプトfix-theme-colors.shを作成:

#!/bin/bash
echo "=== テーマ色ハードコード修正開始 ==="

WORK_DIR="examples/svelte-app/src"

# 成功色の置換
find "$WORK_DIR" -name "*.svelte" -exec sed -i 's/#28a745/var(--color-success)/g' {} \;
find "$WORK_DIR" -name "*.svelte" -exec sed -i 's/#52c41a/var(--color-button-success)/g' {} \;

# エラー色の置換  
find "$WORK_DIR" -name "*.svelte" -exec sed -i 's/#dc3545/var(--color-error)/g' {} \;
find "$WORK_DIR" -name "*.svelte" -exec sed -i 's/#ff0000/var(--color-error)/g' {} \;

# 背景色・テキスト色・RGBA値の包括的置換...

感想

逐次処理ではAPI課金がいくらあっても足らないので一括処理を提案します。こちらのほうが結果も一貫性があるはずです。

指示11: 全力実装の指示

ユーザープロンプト:

遠慮せず全力を尽くしてください

Clineの全力実装:

  1. 60以上のCSS変数定義 - app.cssへの包括的カラーシステム構築
  2. App.svelteテーマエンジン拡張 - applyTheme関数の完全実装
  3. 一括置換スクリプト実行 - 143箇所 → 0箇所(100%解決)
  4. 品質保証: 全テスト成功(120/120)、リント・構文エラー0件

感想

Actフェーズに切り替えるタイミングで全力実装を指示。これはCloude4の発表タイミングでanthropicのドキュメントにプロンプト例としてあったような気がしましたが今見つからなかったので気のせいかも。

しばらく全力で頑張ってくれて全てのハードコードカラーがなくなりました。ただ動作確認するとまだだめそう。一旦きりがいいのでまた別セッションに移ります。

フェーズ3: UIコンポーネント統一化

指示12: ボタン配色問題の特定

ユーザープロンプト:

@/examples/svelte-app/src/app.css と@/examples/svelte-app/src/App.svelte でアプリのカラーテーマを制御しています。しかし課題がいくつかあるようです。フィードバックするので一つづつ解決していきましょう。
- ダークモードの際に以下のセクションの背景色が白ですが文字色も白なので読めません
@/examples/svelte-app/src/components/screens/AccountScreen.svelte
'''
  .warning-section {
'''
- ダークモードで以下の箇所のように黄色に対してグレーの文字では文字が読めません

@/examples/svelte-app/src/components/settings/ExportSecretKey.svelte
'''
  .export-button {
    background-color: var(--color-warning);
    color: var(--color-text-dark);
  }
'''
- ライトモードで@/examples/svelte-app/src/components/settings のコンポーネントのいくつかで白ボタンに対して背景色も文字色も白の箇所があります。ボタンの配色には統一感がほしいです。
- ライトモードで @/examples/svelte-app/src/components/ProfileEditor.svelte のフォームは枠線がないため存在がわかりません

Clineの対応:

  • 問題箇所の特定(AccountScreen.svelte, ExportSecretKey.svelte等)
  • CSS変数システムの不具合を発見
  • コントラスト比の問題を特定

感想

最終局面なのでめちゃくちゃ具体的な指示を入れています。しかし結論としてはあまりうまくいかないのでアプローチを変えていきます。実際に表示してみるとボタンのラベルが見えない問題は解決していなかったのです。

指示13: アプローチ変更の提案

ユーザープロンプト:

ボタンの配色問題は解決していません。アプローチを変えましょう:
- ボタンは4~5パターンのUIコンポーネントとして定義
- 今後は直接buttonタグは使わないように
まずボタンの種類を提案してください

Clineの提案:

  • Button.svelte (プライマリアクション)
  • DangerButton.svelte (危険なアクション)
  • WarningButton.svelte (注意レベルのアクション)
  • SecondaryButton.svelte (セカンダリアクション)
  • IconButton.svelte (アイコンボタン)

指示14: 統一コンポーネント実装

ユーザープロンプト:

それで実装して

Clineの対応:

  1. 5つのUIコンポーネント作成 - 統一されたプロパティ設計
  2. テーマ対応CSS実装 - 各ボタンタイプでのCSS変数活用
  3. アクセシビリティ考慮 - フォーカス状態、無効化状態の適切な実装

指示15: 既存コード全面置き換え

ユーザープロンプト:

buttonを直接使用している箇所を洗い出して全て入れ替えてください

Clineの対応:

  1. 全件検索: <button[^>]*class=パターンで検索実行
  2. 段階的置き換え: ProfileEditor.svelte、ExportSecretKey.svelte、RelayManagement.svelte等
  3. 不要CSS削除: 各コンポーネントから個別ボタンスタイルを整理

感想

コンポーネント切り出しをおこなって問題箇所の限定化を図っています。

指示16: CSS変数問題の根本解決

ユーザープロンプト:

`--color-button-secondary is not defined`のようにスタイルが反映されない場合があります。原因を調査して対応を考えて

Clineの根本解決:

  1. 循環参照の発見: root.style.setProperty('--color-button-secondary', 'var(--color-button-secondary)');
  2. 完全修正: 具体的な色値を直接指定する形に変更
  3. 全CSS変数チェック: 60以上の変数で循環参照を完全排除

感想

ここで原因をclineが突き止めました。自分は指示だけでデバッグしてません。
バグを修正し反映されるようになります。

指示17: 最終コミット

ユーザープロンプト:

ダークモードの実装が完了したのでコミットして

Clineの最終作業:

git commit -m "feat(ui): implement dark mode and unified button components

- Add dark/light theme system with proper contrast
- Create unified UI button components (Button, DangerButton, WarningButton, SecondaryButton, IconButton)
- Fix CSS variable circular references in App.svelte
- Improve readability in dark mode for warning sections and forms
- Add theme settings component with auto/light/dark options
- Unify button styling across all settings components"

感想

お疲れ様でした!

まとめ

指示回数と成果

合計17回の指示で、既存アプリケーションへの包括的なダークモード実装が完了しました。

定量的成果:

  • ハードコード色: 143箇所 → 0箇所(100%解決)
  • 新規CSS変数: 60以上定義
  • 新規UIコンポーネント: 5個作成

軌道修正が必要だった箇所

  1. ブラウザツールの制約: ダークモード選択ができない問題で、一時的なコード修正が必要
  2. CSS変数の循環参照: 技術的な根本問題の発見と解決
  3. アプローチの転換: 個別修正からボタンコンポーネント統一への戦略変更

実装されたダークモード切り替えシステムの詳細

テーマ管理システムの構成

完成したダークモード実装は以下の3つのコンポーネントで構成されています:

  1. メインテーマエンジン(App.svelte) - CSS変数の動的設定
  2. テーマ設定UI(theme-settings.svelte) - ユーザーインターフェース
  3. 状態管理ストア(app-state.ts) - 設定の永続化

1. メインテーマエンジン(App.svelte)

applyTheme関数が60以上のCSS変数を動的に設定し、ライト・ダーク両テーマを切り替えます:

// テーマの適用
function applyTheme(theme: ThemeMode) {
  if (typeof window === 'undefined') return;

  const root = document.documentElement;
  const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
  const isDark = theme === 'dark' || (theme === 'auto' && prefersDark);

  if (isDark) {
    // Modern Dark Theme
    root.style.setProperty('--color-text', '#FFFFFF');
    root.style.setProperty('--color-titles', '#FFFFFF');
    root.style.setProperty('--color-primary', '#9E7EF9');
    root.style.setProperty('--color-card', '#111111');
    root.style.setProperty('--color-background', '#000000');
    root.style.setProperty('--color-border', '#222222');
    // ... 60以上のCSS変数設定
  } else {
    // Light Theme (Purple Breeze)
    root.style.setProperty('--color-text', '#535465');
    root.style.setProperty('--color-titles', '#11142D');
    root.style.setProperty('--color-primary', '#6C5DD3');
    root.style.setProperty('--color-card', '#FFFFFF');
    root.style.setProperty('--color-background', '#F6F6F8');
    root.style.setProperty('--color-border', '#E4E4E4');
    // ... 60以上のCSS変数設定
  }
}

システムテーマ変更の自動監視:

onMount(async () => {
  // 初期テーマの適用
  applyTheme($currentTheme);

  // システムテーマ変更の監視
  if (typeof window !== 'undefined') {
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    
    const handleSystemThemeChange = () => {
      if ($currentTheme === 'auto') {
        applyTheme('auto');
      }
    };

    mediaQuery.addEventListener('change', handleSystemThemeChange);
  }

  // テーマストアの変更を監視
  currentTheme.subscribe((theme) => {
    applyTheme(theme);
  });
});

2. テーマ設定UI(theme-settings.svelte)

ユーザーが3つのテーマオプションから選択できるシンプルなUIを提供:

<script lang="ts">
import { type ThemeMode, currentTheme } from '../../store/app-state.js';

let selectedTheme: ThemeMode = 'auto';

// 現在の設定を読み込み
currentTheme.subscribe((value) => {
  selectedTheme = value;
});

const handleThemeChange = (event: Event) => {
  const target = event.target as HTMLSelectElement;
  const newTheme = target.value as ThemeMode;
  selectedTheme = newTheme;
  currentTheme.set(newTheme);

  // テーマ変更のフィードバック表示
  const message = document.createElement('div');
  message.textContent = $i18n.t.settings.theme.changed;
  // ... スタイル設定とアニメーション
};
</script>

<select bind:value={selectedTheme} on:change={handleThemeChange}>
  <option value="auto">{$i18n.t.settings.theme.auto}</option>
  <option value="light">{$i18n.t.settings.theme.light}</option>
  <option value="dark">{$i18n.t.settings.theme.dark}</option>
</select>

3. 実装された機能

✅ 実現された要件:

  • 3つのテーマモード: システム設定・ライト・ダーク
  • 即座反映: リロード不要でCSS変数が動的変更
  • 設定永続化: LocalStorage連携による次回起動時の設定引き継ぎ
  • システム連携: OSのダークモード設定変更の自動検知・反映
  • 視覚的フィードバック: テーマ変更時の通知表示

🎨 カラーシステム:

  • Modern Dark: 黒背景(#000000)+ 紫アクセント(#9E7EF9)
  • Purple Breeze: 薄灰背景(#F6F6F8)+ 紫アクセント(#6C5DD3)
  • 60以上のCSS変数: ボタン、状態色、透明度、ホバー効果まで完全カバー

🔄 切り替えの流れ:

  1. ユーザーがtheme-settings.svelteで選択
  2. currentThemeストアに設定値を保存(LocalStorage連携)
  3. App.svelteのapplyTheme関数が全CSS変数を一括更新
  4. 全コンポーネントが新しいテーマで即座に再描画

いくらかかった?

ブログ用の資料作成の料金と混ざったのでだいたいの数字です。

  • タスク1: $8
  • タスク2: $2
  • タスク3: $8

うわー18ドル程度かかっています。2,600円です。たっか…。個人でこれを続けるのは厳しい感じがしますね。企業でこの金額でダークモード実装できるって考えれば普通にあり。

最後に

どうでしょうか、いろいろやってはいますが、実作業時間としてはフルタイムで働きながら夜の間だけ実装して3日で完了しました。こうやって書いてみるとめちゃくちゃ指示しているように見えるかもしれませんが、実際はほったらかしで寝てた日が多かったです。

まったく何もない既存のアプリからデザインシステムをあとから実装するのは、アプリが大きければ大きいほどこれまでは非常にめんどくさい大仕事という感じでしたが、今後はそんなことはなくなりそうですね。

Discussion