Skip to content

feat: browse reservations on a resource-by-time timeline - #140

Open
KinjiKawaguchi wants to merge 7 commits into
mainfrom
feat/web-timeline
Open

KinjiKawaguchi wants to merge 7 commits into
mainfrom
feat/web-timeline

Conversation

@KinjiKawaguchi

Copy link
Copy Markdown
Member

Why

予約の閲覧手段が Google Calendar の UI に依存している。だが予約の実体は「リソース × 時間」の二次元表であり、Calendar は「1人の予定を時間軸で追う」ための UI なので、GPU を横並びで見比べる用途に向かない。複数カレンダーを重ねると色が潰れて比較にならない。

リソースを縦軸・時間を横軸に取ったタイムラインを Web 画面として追加する。

What

閲覧専用の画面を1枚追加する。予約の作成・変更・キャンセルは既存の Slack と MCP が担い、この画面からは行わない。

  • 縦にリソース(サーバーごとの GPU と部屋)、横に時間。予約が1件もないリソースも行として残す
  • 表示期間は 1日 / 3日 / 1週間 / 1か月(?days= でも指定可、上限60日)。当日0時から始まる
  • 予約ブロックは所有者ごとに色が変わる。ホバーで正確な時刻と備考
  • 現在時刻に赤い縦線
  • 同一リソースで時間が重なる予約は上下に段を分けて両方表示する
  • 設定から外れたリソースの予約は末尾にまとめる(黙って消すと画面が「空いている」と嘘をつくため)

WEB_LISTEN_ADDR 未設定なら機能無効という、MCP と同じオプトイン方式にしている。

How

認証を設けていない。 docs/ADMIN_GUIDE_ja.md が既に前提としている「同一 LAN 内、インターネットへの公開は範囲外」に依存する判断。書き込みの経路がないためリスクは情報の露出に限られる。露出を抑えるため、所有者はメールアドレスのローカルパートのみ表示する。備考はそのまま出るため、その旨をドキュメントに明記した。

書き込み系を足す段になれば認証は必須になるが、Topcoat は topcoat-session / topcoat-cookie を同梱しており、既存の McpTokenRepository と同型の Web 専用トークンポートを足せば OAuth なしで済む。

依存の受け渡しが唯一の設計上の難所だった。 Topcoat の #[page] はグローバル関数で型パラメータを持てず、app_context は Rust の型をキーに値を引く。一方ユースケースはリポジトリ実装でジェネリックなので、そのままでは画面が参照する型を書けない。interface/web 側に ReservationQuery を定義してトレイトオブジェクトへ均し、画面がリポジトリ実装を知らずに済むようにした。

Tailwind の CSS はバイナリに埋め込んでいる。 標準構成では生成 CSS がディスク上の dist/assets に置かれ実行時に読まれるため、単一バイナリ配布が崩れる。build.rs が出力する $OUT_DIR/tailwind.cssinclude_str!<style> に直接埋めることで、cargo build --release だけでビルドが完結し、install.sh / systemd unit / tar.gz の構成は変更不要になった。

web フィーチャの下に置き既定では無効。crates.io 経由のライブラリ利用者に Topcoat の依存を負わせないため。配布バイナリでは有効にするので release.yml--features web を追加した。

検証

  • テスト 165件通過(web 有効時。うち結合テスト6件は実際に HTTP で叩いている)。web 無効時も 148件通過
  • clippy は両フィーチャ構成で警告ゼロ
  • release ビルドで単一バイナリ 29.8MB、アセットの外部配置なし

結合テストは、Topcoat の .discover() がライブラリ側に置いた #[page] をリンク時に拾えているかの検証を兼ねている(拾えていなければ404が返る)。

なお view! マクロで条件付きの class を書くと静的な class を上書きしてしまい、レイアウトの土台ごと失われる挙動があった。class! マクロを使えば解決するが、再発防止に回帰テストを1件置いてある。

🤖 Generated with Claude Code

https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt

KinjiKawaguchi and others added 3 commits July 31, 2026 17:35
Google Calendarは1人の予定を時間軸で追うためのUIで、複数リソースの空きを
横並びで比べる用途には向かない。予約の実体はリソース×時間の二次元表なので、
リソースを縦・時間を横に取ったタイムラインをTopcoatで用意する。

閲覧専用とし、予約の作成・変更・キャンセルは既存のSlackとMCPが担う。
書き込みの経路がないため認証は設けず、ADMIN_GUIDEが既に前提としている
LAN内運用に依存する。露出を抑えるため所有者はメールのローカルパートのみ表示する。

crates.io経由のライブラリ利用者にTopcoatの依存を負わせないよう、
webフィーチャの下に置いて既定では無効にする。TailwindのCSSはビルド時に生成して
バイナリへ埋め込み、単一バイナリでの配布を保てるようにした。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt
有効化にwebフィーチャが要ること、認証がなくLAN内運用が前提であること、
備考は表示されるので機微な内容を書かない運用が要ることを明記する。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt
webフィーチャは既定で無効なため、指定がないとリリースされたバイナリから
Web画面が抜け落ちる。ライブラリとしての依存は既定のままで変わらない。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt
Copilot AI review requested due to automatic review settings July 31, 2026 08:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

KinjiKawaguchi and others added 2 commits July 31, 2026 17:54
目盛りを一定時間の加算で置いていたため、夏時間の始まる日(現地の一日が23時間
しかない)を跨ぐと刻みが日付の変わり目からずれ、その日以降の見出しが消えていた。
現地の日付を軸に、各日の始まりから積む方式へ改める。

真夜中に夏時間が切り替わる地域では0時そのものが存在しない日がある。そこで
「その日で最初に存在する時刻」を日の始まりとして扱い、見出しを落とさないようにした。
表示範囲の決め方も一定時間の加算から現地日付での計算に揃えている。

あわせて表示範囲の終端を目盛りから外した。そこは範囲の終わりであって内側ではなく、
翌日の見出しを置くと表示していない日まで並んでいるように見えるため。
参照されていない TimelineBlock.id もここで落とす。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt
行の高さとブロックの位置で同じ余白を別々の数値として書いていた。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt
Copilot AI review requested due to automatic review settings July 31, 2026 08:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@KinjiKawaguchi KinjiKawaguchi changed the title feat(web): 予約タイムラインの閲覧画面を追加 feat: browse reservations on a resource-by-time timeline Aug 2, 2026
…c API

The crate is published as a library, so whatever stays `pub` is part of what
SemVer promises. The view and page items carry Topcoat's own types in their
signatures, and Topcoat is a 0.x release that says breaking changes are to be
expected — each one would have forced a major bump here for a reason that has
nothing to do with this crate's own contract.

Only `serve`, `serve_on` and the `query` port remain public. That is what the
composition root needs, and none of them name a Topcoat type, so the framework
can break without touching what this crate promises.

The timing matters: widening visibility later is a minor change, narrowing it
is a major one, and 1.7.0 has not shipped yet.

Restricting the visibility also let the compiler point out that
`TimelineRow::is_empty` was never used outside tests, so it is gone too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt
Copilot AI review requested due to automatic review settings August 2, 2026 10:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

The Rust API is already insulated from Topcoat, but the environment
variables, the URL layout and the page itself are what operators actually
depend on, and none of those are covered by the crate's public API. Saying
now that they may move in a minor release keeps the option of reshaping the
feature later without a major bump — and claiming stability afterwards is
always available, whereas claiming instability afterwards is not.

This matches how the GPU usage observer in section 5 is already labelled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016uSBf6WpYKseYkhxDLKCLt
Copilot AI review requested due to automatic review settings August 2, 2026 11:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants