#BuildWithBuildAgentTokyo にチャレンジです。
今回は、PDI(Personal Developer Instance)の Build Agent を使って、 「フラッシュ暗算」と「漢字書取り」ができる学習アプリ を作ってみました。
ServiceNowのUI(Core UIや標準のポータル)が今一つ、利用者に受けが良くないので、他のUIでどこまで作れるのか、ゲーム感覚で取り組めるものを題材に作ってみようと思って実施しました。
作る過程で 「数字が表示されないフラッシュ暗算」 という、なかなか味わい深いバグに出会いました。原因がServiceNowのUIページならではのものだったので、後半ではその原因と直し方も紹介します。
#BuildWithBuildAgentTokyo とは?
グローバルで開催されてきた開発チャレンジ「#BuildWith」の日本版です。
開催期間は 2026年9月7日〜10月16日 です。
詳細はこちらから!
作ったもの
メニューから 「フラッシュ暗算」 と 「漢字書取り」 の2つのゲームを選べます。どちらも 合格すると花火の演出 でお祝いしてくれて、プレイ結果は 実行履歴 として ServiceNow のテーブルに保存されます。
| 機能 | 内容 |
|---|---|
| フラッシュ暗算 | 数字が一定間隔で次々と表示され、最後に合計を答える。1回5問、4問正解で合格 |
| レベル選択(暗算) | レベル1(初級)〜レベル5(達人)の5段階。桁数・表示個数・表示間隔がレベルごとに変わる |
| 結果発表 | 不合格のときは、各問題の式・正解・自分の回答を一覧で振り返れる |
| 漢字書取り | 読みが表示され、画面上のキャンバスに 指やマウスで漢字を手書き。書き終えたら正解の漢字を表示し、自分で ⭕/❌ を判定する |
| 学年選択(漢字) | 小学1年生〜中学3年生の9段階。1回15問、12問正解で合格 |
| 花火演出 | 合格するとアニメーションの花火で「おめでとう!!」 |
| 実行履歴 | ゲーム種別・レベル・スコア・正解数・合否・完了日時を一覧表示 |
フラッシュ暗算のレベル
| レベル | 桁数 | 表示個数 | 表示間隔 |
|---|---|---|---|
| レベル1(初級) | 1桁 | 3個 | 2.0秒 |
| レベル2(中級) | 1桁 | 5個 | 1.5秒 |
| レベル3(上級) | 2桁 | 5個 | 1.0秒 |
| レベル4(超上級) | 2桁 | 7個 | 0.8秒 |
| レベル5(達人) | 3桁 | 10個 | 0.5秒 |
レベル5は 3桁×10個を0.5秒間隔 なので、大人でもかなり難しいです。
ちなみに私は0点でした😇
漢字書取り
手書きの判定は、あえて 自己採点 にしています。書いたあとに正解の漢字が表示されるので、見比べて ⭕ か ❌ を押します。
自己採点にした理由は、手書き認識まで作り込むと大がかりになるため、まずは動くものを作ることを優先しただけです。使ってみると十分に実用的だったので、そのまま採用しました。(外部のAIとか使って採点させても良かったかも・・・)
環境
| 項目 | 内容 |
|---|---|
| ServiceNow | PDI(Australia Patch3) |
| Build Agent | Build Agent (Trial) 2.3.3 |
| UI | UI page(x_<スコープ>_main.do) |
| データ | カスタムテーブル「Game History」(実行履歴) |
インスタンス上のメタデータ一覧
Build Agent に、作成したアプリのメタデータを一覧にしてもらいました。
1️⃣ テーブル(sys_db_object)
| テーブル名 | ラベル | 親テーブル |
|---|---|---|
x_276493_buildwi_0_game_history |
Game History | なし(独立テーブル) |
2️⃣ Game History のカラム(sys_dictionary)
| フィールド | ラベル | 型 | 必須 | デフォルト値 |
|---|---|---|---|---|
game_type |
Game Type | Choice | ✅ | — |
level |
Level | String(100) | ✅ | — |
score |
Score | Integer | — | 0 |
correct_answers |
Correct Answers | Integer | — | 0 |
total_questions |
Total Questions | Integer | — | 0 |
passed |
Passed | True/False | — | false |
completed_at |
Completed At | Date/Time | — | — |
このほか、sys_id・sys_created_on などのシステム標準フィールドがあります。
3️⃣ 選択肢(sys_choice)— game_type
| 値 | ラベル | 順序 |
|---|---|---|
flash_anzan |
フラッシュ暗算 | 1 |
kanji_writing |
漢字書取り | 2 |
4️⃣ UI ページ(sys_ui_page)
| 名前 | エンドポイント | 内容 |
|---|---|---|
| main | x_276493_buildwi_0_main.do |
React で作ったメイン画面(SPA) |
5️⃣ UX ライブラリアセット(sys_ux_lib_asset)
| アセット | 内容 |
|---|---|
main |
React の画面をビルドした JavaScript(バンドル) |
main.js.map |
バンドルのソースマップ |
ソースマップから分かる、画面側のファイル構成は次のとおりです。
src/client/
├── main.tsx # エントリポイント(Reactの起動処理)
├── app.tsx # メインのAppコンポーネント(画面の切り替え)
├── styles/
│ └── app.css # スタイルシート
├── components/
│ ├── Menu.tsx # メニュー画面
│ ├── FlashAnzan.tsx # フラッシュ暗算:レベル選択
│ ├── FlashGame.tsx # フラッシュ暗算:ゲーム本体
│ ├── FlashResult.tsx # フラッシュ暗算:結果画面
│ ├── KanjiWriting.tsx # 漢字書取り:学年選択
│ ├── KanjiGame.tsx # 漢字書取り:ゲーム本体
│ ├── HandwritingCanvas.tsx # 手書き入力キャンバス
│ ├── History.tsx # 実行履歴画面
│ └── Fireworks.tsx # 花火演出
├── data/
│ ├── flashLevels.ts # フラッシュ暗算のレベル定義 ← 今回のバグの箇所
│ └── kanjiData.ts # 漢字データ
└── utils/
└── api.ts # 実行履歴の保存(Table API)
6️⃣ その他のメタデータ
| タイプ | 件数 | 内容 |
|---|---|---|
| sys_app_application | 1 | アプリケーションメニュー |
| sys_app_module | 1 | モジュール「ゲーム」(UI ページを開く) |
| sys_choice_set | 1 | game_type の選択肢セット |
| sys_documentation | 複数 | フィールドのラベル(英語) |
| ua_table_licensing_config | 1 | テーブルのライセンス設定 |
つまずいたこと:数字が表示されないフラッシュ暗算
症状
Build Agent が作ったアプリを動かしてみると、フラッシュ暗算で 数字が表示されるはずの枠が空っぽのまま でした。

(ずっとこんな風に数字が表示されない)
しかもそのまま待っていると、何事もなかったかのように 「合計はいくつですか?」と回答を求められます。

見えていない数字の合計は答えようがありません😇 さらに調べると、何を入力しても不正解 になり、合格したときの花火画面も真っ白 になっていました。
原因:prototype.js が Array.from を上書きしていた
ソースを見ると、出題する数字は次のように作られていました。
export function genNumbers(digits: number, count: number): number[] {
const min = Math.pow(10, digits - 1)
const max = Math.pow(10, digits) - 1
return Array.from({ length: count }, () => Math.floor(Math.random() * (max - min + 1)) + min)
}
一見なんの問題もないコードで、実際に ローカルのブラウザでは正常に動きます。
ポイントは、この UI ページが ServiceNow 標準の古いライブラリ scripts/lib/prototype.js を読み込んでいることでした。prototype.js は、ブラウザ標準の Array.from を 独自の関数で上書き します。
function $A(iterable) {
if (!iterable) return [];
if ('toArray' in Object(iterable)) return iterable.toArray();
var length = iterable.length || 0,
results = new Array(length);
while (length--) results[length] = iterable[length];
return results;
}
// ...
Array.from = $A;
上書き後の Array.from は 第2引数(要素を作る関数)を無視 します。そのため genNumbers() は乱数の配列ではなく [undefined, undefined, undefined] を返していました。
その結果、次のことが起きていました。
-
数字が表示されない:表示する値が
undefinedになり、中身が空の数字欄が描画される - 時間が経つと回答画面に進む:タイマー自体は正常に動いている
-
何を入力しても不正解:正解が
undefinedの合計、つまりNaNになる - 花火画面が真っ白:花火の粒も同じ書き方で作っていたため、描画時にエラーになる
ServiceNow の UI ページで React の JavaScript を動かすときは、ページが読み込む古いライブラリが標準 API を上書きしていないか に注意が必要です。インスタンス上でだけ壊れる という形で現れたりもするようです。
直し方
Array.from の第2引数を使わず、普通のループで配列を作る ように変えました。
export function genNumbers(digits: number, count: number): number[] {
const min = Math.pow(10, digits - 1)
const max = Math.pow(10, digits) - 1
- return Array.from({ length: count }, () => Math.floor(Math.random() * (max - min + 1)) + min)
+ // UIページが読み込む prototype.js が Array.from を上書きし mapFn を無視するため、ループで生成する
+ const nums: number[] = []
+ for (let i = 0; i < count; i++) {
+ nums.push(Math.floor(Math.random() * (max - min + 1)) + min)
+ }
+ return nums
}
花火画面(Fireworks.tsx)も同じ書き方をしていたので、同様に修正しています。
補足:修正の反映方法
今回のバグの箇所の発見、修正もBuildAgentに手伝ってもらってます。 不具合個所の発見、修正方法の提案、承認といった流れで対応できてます。 また、切り分けのためServiceNow SDK(`@servicenow/sdk`)でインスタンスからアプリを取り込み、ローカルで修正・ビルドして反映も行って同じ結果になりました。デモ動画
まとめ
私自身はコードを1行も書かずに、画面のイメージや遊び方のルールを言葉で伝えるだけ でアプリが完成しました。「レベルを5段階にしたい」「合格したら花火を出したい」といった思いつきも、その場で伝えればすぐに形にしてくれるので、作っていてとても楽しかったです。
一方で、今回のバグの箇所は画面を見ただけでは、原因の見当がつきにくいものでした。AI が書いたコードのため、同じAIに発見させる と非常に楽ですね。
お前(AI)が出したバグなんだからお前で何とかしろ!ということです www
★ ServiceNow World Forum Tokyo 2026 ★
ServiceNow World Forum Tokyo 2026が2026年10月27日(火)〜28日(水)、グランドプリンスホテル新高輪で開催されます。#BuildWithBuildAgentTokyo の優秀作品は、会場の Vibe Code ラウンジで紹介されるとのこと。





