[{"data":1,"prerenderedAt":1605},["ShallowReactive",2],{"navigation":3,"doc-/frontend/api-access-chain":217,"docs-all-paths":1550},[4,19,39,63,87,111,127,195,211],{"title":5,"path":6,"stem":7,"children":8},"Welcome","/welcome","01.welcome",[9,11,15],{"title":5,"path":6,"stem":10},"01.welcome/index",{"title":12,"path":13,"stem":14},"Astar とは（5分）","/welcome/product-in-5-min","01.welcome/01.product-in-5-min",{"title":16,"path":17,"stem":18},"事業文脈","/welcome/business-context","01.welcome/02.business-context",{"title":20,"path":21,"stem":22,"children":23},"Getting Started","/getting-started","02.getting-started",[24,27,31,35],{"title":25,"path":21,"stem":26},"はじめかた","02.getting-started/index",{"title":28,"path":29,"stem":30},"リポジトリ取得と環境構築","/getting-started/repo-setup","02.getting-started/01.repo-setup",{"title":32,"path":33,"stem":34},"日々の開発ループ","/getting-started/dev-loop","02.getting-started/02.dev-loop",{"title":36,"path":37,"stem":38},"検証の 4 層","/getting-started/verification-tiers","02.getting-started/03.verification-tiers",{"title":40,"path":41,"stem":42,"children":43},"Architecture","/architecture","03.architecture",[44,47,51,55,59],{"title":45,"path":41,"stem":46},"アーキテクチャ","03.architecture/index",{"title":48,"path":49,"stem":50},"マルチテナンシーと RLS","/architecture/multi-tenancy-rls","03.architecture/01.multi-tenancy-rls",{"title":52,"path":53,"stem":54},"認可モデル","/architecture/authorization","03.architecture/02.authorization",{"title":56,"path":57,"stem":58},"AI サブシステム","/architecture/ai-subsystem","03.architecture/03.ai-subsystem",{"title":60,"path":61,"stem":62},"データモデル","/architecture/data-model","03.architecture/04.data-model",{"title":64,"path":65,"stem":66,"children":67},"Backend","/backend","04.backend",[68,71,75,79,83],{"title":69,"path":65,"stem":70},"バックエンド","04.backend/index",{"title":72,"path":73,"stem":74},"層構造","/backend/layering","04.backend/01.layering",{"title":76,"path":77,"stem":78},"規約","/backend/conventions","04.backend/02.conventions",{"title":80,"path":81,"stem":82},"テスト","/backend/testing","04.backend/03.testing",{"title":84,"path":85,"stem":86},"マイグレーション","/backend/migrations","04.backend/04.migrations",{"title":88,"path":89,"stem":90,"children":91},"Frontend","/frontend","05.frontend",[92,95,99,103,107],{"title":93,"path":89,"stem":94},"フロントエンド","05.frontend/index",{"title":96,"path":97,"stem":98},"モジュール内部構造","/frontend/module-anatomy","05.frontend/01.module-anatomy",{"title":100,"path":101,"stem":102},"API アクセスの一本道","/frontend/api-access-chain","05.frontend/02.api-access-chain",{"title":104,"path":105,"stem":106},"i18n","/frontend/i18n","05.frontend/03.i18n",{"title":108,"path":109,"stem":110},"デザインシステム","/frontend/design-system","05.frontend/04.design-system",{"title":112,"path":113,"stem":114,"children":115},"Go Agent And Cli","/go-agent-and-cli","06.go-agent-and-cli",[116,119,123],{"title":117,"path":113,"stem":118},"Go agent と CLI","06.go-agent-and-cli/index",{"title":120,"path":121,"stem":122},"Go agent","/go-agent-and-cli/go-agent","06.go-agent-and-cli/01.go-agent",{"title":124,"path":125,"stem":126},"CLI","/go-agent-and-cli/cli","06.go-agent-and-cli/02.cli",{"title":128,"path":129,"stem":130,"children":131},"Modules","/modules","07.modules",[132,135,139,143,147,151,155,159,163,167,171,175,179,183,187,191],{"title":133,"path":129,"stem":134},"モジュール","07.modules/index",{"title":136,"path":137,"stem":138},"テーブル","/modules/table","07.modules/01.table",{"title":140,"path":141,"stem":142},"ドキュメントツリー","/modules/document","07.modules/02.document",{"title":144,"path":145,"stem":146},"AIチャット","/modules/ai","07.modules/03.ai",{"title":148,"path":149,"stem":150},"認証・権限","/modules/auth","07.modules/04.auth",{"title":152,"path":153,"stem":154},"AI取込","/modules/organize","07.modules/05.organize",{"title":156,"path":157,"stem":158},"ストレージ","/modules/storage","07.modules/06.storage",{"title":160,"path":161,"stem":162},"NAS同期","/modules/sync","07.modules/07.sync",{"title":164,"path":165,"stem":166},"ワークスペース","/modules/workspace","07.modules/08.workspace",{"title":168,"path":169,"stem":170},"テナントとメンバーシップ","/modules/tenant-membership","07.modules/09.tenant-membership",{"title":172,"path":173,"stem":174},"検索","/modules/search","07.modules/10.search",{"title":176,"path":177,"stem":178},"表示ビュー","/modules/display","07.modules/11.display",{"title":180,"path":181,"stem":182},"テンプレート","/modules/template","07.modules/12.template",{"title":184,"path":185,"stem":186},"メール","/modules/mail","07.modules/13.mail",{"title":188,"path":189,"stem":190},"電話","/modules/phone","07.modules/14.phone",{"title":192,"path":193,"stem":194},"リアルタイム","/modules/realtime","07.modules/15.realtime",{"title":196,"path":197,"stem":198,"children":199},"Infra And Deploy","/infra-and-deploy","08.infra-and-deploy",[200,203,207],{"title":201,"path":197,"stem":202},"インフラとデプロイ","08.infra-and-deploy/index",{"title":204,"path":205,"stem":206},"環境の種類","/infra-and-deploy/environments","08.infra-and-deploy/01.environments",{"title":208,"path":209,"stem":210},"セルフホスト構成","/infra-and-deploy/self-host","08.infra-and-deploy/02.self-host",{"title":212,"path":213,"stem":214,"children":215},"docs-map","/docs-map","09.docs-map/index",[216],{"title":212,"path":213,"stem":214},{"id":218,"title":100,"body":219,"description":1545,"extension":1546,"meta":1547,"navigation":342,"path":101,"seo":1548,"stem":102,"__hash__":1549},"docs/05.frontend/02.api-access-chain.md",{"type":220,"value":221,"toc":1533},"minimark",[222,226,230,241,252,263,270,569,666,673,682,973,976,1039,1043,1050,1260,1270,1274,1289,1310,1352,1367,1377,1382,1396,1429,1454,1468,1472,1483,1510,1513,1516,1529],[223,224,100],"h1",{"id":225},"api-アクセスの一本道",[227,228,229],"p",{},"Astar Management のフロントエンドで backend の API を叩く経路は、常に同じ形をしています。",[231,232,237],"pre",{"className":233,"code":235,"language":236},[234],"language-text","Component → composable (TanStack Query) → repository (useApi() + apiRequest())\n","text",[238,239,235],"code",{"__ignoreMap":240},"",[227,242,243,251],{},[244,245,246,247,250],"strong",{},"Component が repository や ",[238,248,249],{},"useApi()"," を直接呼ぶことは禁止です。"," 必ず composable を経由します。この一本道を崩さないことが、キャッシュ管理・エラーハンドリング・テナント分離を一貫させる前提になっています。",[253,254,256,257,259,260],"h2",{"id":255},"repository-層-useapi-apirequest","repository 層 — ",[238,258,249],{}," + ",[238,261,262],{},"apiRequest()",[227,264,265,266,269],{},"repository は API 呼び出しそのものをラップする層です。",[238,267,268],{},"tag"," モジュールの実装:",[231,271,275],{"className":272,"code":273,"language":274,"meta":240,"style":240},"language-typescript shiki shiki-themes github-light github-dark","// app/modules/tag/repositories/TagRepository.ts\nexport const useTagRepository = (): TagRepository => {\n  const api = useApi()\n\n  const listTags = async (): Promise\u003CTagResponse[]> => {\n    return apiRequest(\n      () => api.GET('/api/v1/tags'),\n      { context: 'TagRepository.listTags' }\n    )\n  }\n\n  const deleteTag = async (id: string): Promise\u003Cvoid> => {\n    await apiRequestVoid(\n      () => api.DELETE('/api/v1/tags/{id}', { params: { path: { id } } }),\n      { context: 'TagRepository.deleteTag', logData: { id } }\n    )\n  }\n\n  // ...\n  return { listTags, deleteTag, /* ... */ }\n}\n","typescript",[238,276,277,286,319,337,344,378,390,414,426,432,438,443,486,497,517,528,533,538,543,549,563],{"__ignoreMap":240},[278,279,282],"span",{"class":280,"line":281},"line",1,[278,283,285],{"class":284},"sJ8bj","// app/modules/tag/repositories/TagRepository.ts\n",[278,287,289,293,296,300,303,307,310,313,316],{"class":280,"line":288},2,[278,290,292],{"class":291},"szBVR","export",[278,294,295],{"class":291}," const",[278,297,299],{"class":298},"sScJk"," useTagRepository",[278,301,302],{"class":291}," =",[278,304,306],{"class":305},"sVt8B"," ()",[278,308,309],{"class":291},":",[278,311,312],{"class":298}," TagRepository",[278,314,315],{"class":291}," =>",[278,317,318],{"class":305}," {\n",[278,320,322,325,329,331,334],{"class":280,"line":321},3,[278,323,324],{"class":291},"  const",[278,326,328],{"class":327},"sj4cs"," api",[278,330,302],{"class":291},[278,332,333],{"class":298}," useApi",[278,335,336],{"class":305},"()\n",[278,338,340],{"class":280,"line":339},4,[278,341,343],{"emptyLinePlaceholder":342},true,"\n",[278,345,347,349,352,354,357,359,361,364,367,370,373,376],{"class":280,"line":346},5,[278,348,324],{"class":291},[278,350,351],{"class":298}," listTags",[278,353,302],{"class":291},[278,355,356],{"class":291}," async",[278,358,306],{"class":305},[278,360,309],{"class":291},[278,362,363],{"class":298}," Promise",[278,365,366],{"class":305},"\u003C",[278,368,369],{"class":298},"TagResponse",[278,371,372],{"class":305},"[]> ",[278,374,375],{"class":291},"=>",[278,377,318],{"class":305},[278,379,381,384,387],{"class":280,"line":380},6,[278,382,383],{"class":291},"    return",[278,385,386],{"class":298}," apiRequest",[278,388,389],{"class":305},"(\n",[278,391,393,396,398,401,404,407,411],{"class":280,"line":392},7,[278,394,395],{"class":305},"      () ",[278,397,375],{"class":291},[278,399,400],{"class":305}," api.",[278,402,403],{"class":298},"GET",[278,405,406],{"class":305},"(",[278,408,410],{"class":409},"sZZnC","'/api/v1/tags'",[278,412,413],{"class":305},"),\n",[278,415,417,420,423],{"class":280,"line":416},8,[278,418,419],{"class":305},"      { context: ",[278,421,422],{"class":409},"'TagRepository.listTags'",[278,424,425],{"class":305}," }\n",[278,427,429],{"class":280,"line":428},9,[278,430,431],{"class":305},"    )\n",[278,433,435],{"class":280,"line":434},10,[278,436,437],{"class":305},"  }\n",[278,439,441],{"class":280,"line":440},11,[278,442,343],{"emptyLinePlaceholder":342},[278,444,446,448,451,453,455,458,462,464,467,470,472,474,476,479,482,484],{"class":280,"line":445},12,[278,447,324],{"class":291},[278,449,450],{"class":298}," deleteTag",[278,452,302],{"class":291},[278,454,356],{"class":291},[278,456,457],{"class":305}," (",[278,459,461],{"class":460},"s4XuR","id",[278,463,309],{"class":291},[278,465,466],{"class":327}," string",[278,468,469],{"class":305},")",[278,471,309],{"class":291},[278,473,363],{"class":298},[278,475,366],{"class":305},[278,477,478],{"class":327},"void",[278,480,481],{"class":305},"> ",[278,483,375],{"class":291},[278,485,318],{"class":305},[278,487,489,492,495],{"class":280,"line":488},13,[278,490,491],{"class":291},"    await",[278,493,494],{"class":298}," apiRequestVoid",[278,496,389],{"class":305},[278,498,500,502,504,506,509,511,514],{"class":280,"line":499},14,[278,501,395],{"class":305},[278,503,375],{"class":291},[278,505,400],{"class":305},[278,507,508],{"class":298},"DELETE",[278,510,406],{"class":305},[278,512,513],{"class":409},"'/api/v1/tags/{id}'",[278,515,516],{"class":305},", { params: { path: { id } } }),\n",[278,518,520,522,525],{"class":280,"line":519},15,[278,521,419],{"class":305},[278,523,524],{"class":409},"'TagRepository.deleteTag'",[278,526,527],{"class":305},", logData: { id } }\n",[278,529,531],{"class":280,"line":530},16,[278,532,431],{"class":305},[278,534,536],{"class":280,"line":535},17,[278,537,437],{"class":305},[278,539,541],{"class":280,"line":540},18,[278,542,343],{"emptyLinePlaceholder":342},[278,544,546],{"class":280,"line":545},19,[278,547,548],{"class":284},"  // ...\n",[278,550,552,555,558,561],{"class":280,"line":551},20,[278,553,554],{"class":291},"  return",[278,556,557],{"class":305}," { listTags, deleteTag, ",[278,559,560],{"class":284},"/* ... */",[278,562,425],{"class":305},[278,564,566],{"class":280,"line":565},21,[278,567,568],{"class":305},"}\n",[570,571,572,608,625,660],"ul",{},[573,574,575,577,578,581,582,585,586,589,590,593,594,596,597,600,601,600,604,607],"li",{},[238,576,249],{},"（",[238,579,580],{},"app/shared/api/composables/useApi.ts","）が型付きの API クライアントを返します。生の ",[238,583,584],{},"fetch","/",[238,587,588],{},"$fetch"," を ",[238,591,592],{},"app/modules/**"," で使うのは lint エラーです。相対 URL の ",[238,595,584],{}," はフロントエンドのオリジンを叩いてしまい、",[238,598,599],{},"apiBaseUrl"," / ",[238,602,603],{},"Authorization",[238,605,606],{},"X-Tenant-Id"," を素通りします（これが原因で本番の公開共有バイナリが壊れた実例があります）。",[573,609,610,600,612,615,616,618,619,621,622,624],{},[238,611,262],{},[238,613,614],{},"apiRequestVoid()"," がエラーハンドリングを統一します。戻り値のある呼び出しは ",[238,617,262],{},"、",[238,620,478],{}," を返す呼び出しは ",[238,623,614],{}," を使います。",[573,626,627,629,630,633,634,589,637,640,641,644,645,577,648,651,652,655,656,659],{},[238,628,262],{}," が投げる ",[238,631,632],{},"ApiError"," は ",[238,635,636],{},".status",[244,638,639],{},"直接"," 持ちます（",[238,642,643],{},".response.status"," のような入れ子はありません）。ステータスコードを見るときは ",[238,646,647],{},"getErrorStatus(err)",[238,649,650],{},"app/shared/api/utils/getErrorStatus.ts","）を使ってください。",[238,653,654],{},".response?.status"," は常に ",[238,657,658],{},"undefined"," を返し、409/503/413 のような分岐を静かに握りつぶします。",[573,661,662,665],{},[238,663,664],{},"context"," オプションはエラーログにどの呼び出しで失敗したかを残すためのものです。省略しないでください。",[253,667,669,670],{"id":668},"composable-層-tanstack-query-readonly","composable 層 — TanStack Query + ",[238,671,672],{},"readonly()",[227,674,675,676,678,679,309],{},"composable が実際の状態管理を担当します。",[238,677,268],{}," モジュールの ",[238,680,681],{},"useTagManagement",[231,683,685],{"className":272,"code":684,"language":274,"meta":240,"style":240},"// app/modules/tag/composables/useTagManagement.ts\nexport function useTagManagement() {\n  const repository = useTagRepository()\n  const queryClient = useQueryClient()\n\n  const { data: tagsData, isPending } = useQuery({\n    queryKey: computed(() => queryKeys.tags.list()),\n    queryFn: () => repository.listTags(),\n  })\n\n  const tags = computed(() => tagsData.value ?? [])\n\n  const createTag = async (request: CreateTagRequest) => {\n    const tag = await repository.createTag(request)\n    await queryClient.invalidateQueries({ queryKey: queryKeys.tags.all() })\n    return tag\n  }\n\n  return {\n    tags: readonly(tags),   // 呼び出し側から書き換えられないようにする\n    isLoadingTags: isPending,\n    createTag,\n  }\n}\n",[238,686,687,692,705,718,732,736,770,792,811,816,820,845,849,877,898,917,924,928,932,938,952,957,963,968],{"__ignoreMap":240},[278,688,689],{"class":280,"line":281},[278,690,691],{"class":284},"// app/modules/tag/composables/useTagManagement.ts\n",[278,693,694,696,699,702],{"class":280,"line":288},[278,695,292],{"class":291},[278,697,698],{"class":291}," function",[278,700,701],{"class":298}," useTagManagement",[278,703,704],{"class":305},"() {\n",[278,706,707,709,712,714,716],{"class":280,"line":321},[278,708,324],{"class":291},[278,710,711],{"class":327}," repository",[278,713,302],{"class":291},[278,715,299],{"class":298},[278,717,336],{"class":305},[278,719,720,722,725,727,730],{"class":280,"line":339},[278,721,324],{"class":291},[278,723,724],{"class":327}," queryClient",[278,726,302],{"class":291},[278,728,729],{"class":298}," useQueryClient",[278,731,336],{"class":305},[278,733,734],{"class":280,"line":346},[278,735,343],{"emptyLinePlaceholder":342},[278,737,738,740,743,746,749,752,755,758,761,764,767],{"class":280,"line":380},[278,739,324],{"class":291},[278,741,742],{"class":305}," { ",[278,744,745],{"class":460},"data",[278,747,748],{"class":305},": ",[278,750,751],{"class":327},"tagsData",[278,753,754],{"class":305},", ",[278,756,757],{"class":327},"isPending",[278,759,760],{"class":305}," } ",[278,762,763],{"class":291},"=",[278,765,766],{"class":298}," useQuery",[278,768,769],{"class":305},"({\n",[278,771,772,775,778,781,783,786,789],{"class":280,"line":392},[278,773,774],{"class":305},"    queryKey: ",[278,776,777],{"class":298},"computed",[278,779,780],{"class":305},"(() ",[278,782,375],{"class":291},[278,784,785],{"class":305}," queryKeys.tags.",[278,787,788],{"class":298},"list",[278,790,791],{"class":305},"()),\n",[278,793,794,797,800,802,805,808],{"class":280,"line":416},[278,795,796],{"class":298},"    queryFn",[278,798,799],{"class":305},": () ",[278,801,375],{"class":291},[278,803,804],{"class":305}," repository.",[278,806,807],{"class":298},"listTags",[278,809,810],{"class":305},"(),\n",[278,812,813],{"class":280,"line":428},[278,814,815],{"class":305},"  })\n",[278,817,818],{"class":280,"line":434},[278,819,343],{"emptyLinePlaceholder":342},[278,821,822,824,827,829,832,834,836,839,842],{"class":280,"line":440},[278,823,324],{"class":291},[278,825,826],{"class":327}," tags",[278,828,302],{"class":291},[278,830,831],{"class":298}," computed",[278,833,780],{"class":305},[278,835,375],{"class":291},[278,837,838],{"class":305}," tagsData.value ",[278,840,841],{"class":291},"??",[278,843,844],{"class":305}," [])\n",[278,846,847],{"class":280,"line":445},[278,848,343],{"emptyLinePlaceholder":342},[278,850,851,853,856,858,860,862,865,867,870,873,875],{"class":280,"line":488},[278,852,324],{"class":291},[278,854,855],{"class":298}," createTag",[278,857,302],{"class":291},[278,859,356],{"class":291},[278,861,457],{"class":305},[278,863,864],{"class":460},"request",[278,866,309],{"class":291},[278,868,869],{"class":298}," CreateTagRequest",[278,871,872],{"class":305},") ",[278,874,375],{"class":291},[278,876,318],{"class":305},[278,878,879,882,885,887,890,892,895],{"class":280,"line":499},[278,880,881],{"class":291},"    const",[278,883,884],{"class":327}," tag",[278,886,302],{"class":291},[278,888,889],{"class":291}," await",[278,891,804],{"class":305},[278,893,894],{"class":298},"createTag",[278,896,897],{"class":305},"(request)\n",[278,899,900,902,905,908,911,914],{"class":280,"line":519},[278,901,491],{"class":291},[278,903,904],{"class":305}," queryClient.",[278,906,907],{"class":298},"invalidateQueries",[278,909,910],{"class":305},"({ queryKey: queryKeys.tags.",[278,912,913],{"class":298},"all",[278,915,916],{"class":305},"() })\n",[278,918,919,921],{"class":280,"line":530},[278,920,383],{"class":291},[278,922,923],{"class":305}," tag\n",[278,925,926],{"class":280,"line":535},[278,927,437],{"class":305},[278,929,930],{"class":280,"line":540},[278,931,343],{"emptyLinePlaceholder":342},[278,933,934,936],{"class":280,"line":545},[278,935,554],{"class":291},[278,937,318],{"class":305},[278,939,940,943,946,949],{"class":280,"line":551},[278,941,942],{"class":305},"    tags: ",[278,944,945],{"class":298},"readonly",[278,947,948],{"class":305},"(tags),   ",[278,950,951],{"class":284},"// 呼び出し側から書き換えられないようにする\n",[278,953,954],{"class":280,"line":565},[278,955,956],{"class":305},"    isLoadingTags: isPending,\n",[278,958,960],{"class":280,"line":959},22,[278,961,962],{"class":305},"    createTag,\n",[278,964,966],{"class":280,"line":965},23,[278,967,437],{"class":305},[278,969,971],{"class":280,"line":970},24,[278,972,568],{"class":305},[227,974,975],{},"要点:",[570,977,978,1002,1011,1028],{},[573,979,980,577,987,990,991,994,995,997,998,1001],{},[244,981,982,983,986],{},"queryKey は必ず ",[238,984,985],{},"queryKeys"," ファクトリ経由",[238,988,989],{},"app/shared/api/query-keys.ts","）。手書きの配列（",[238,992,993],{},"['tags', id]","）は禁止です。",[238,996,985],{}," の全キーは先頭に ",[238,999,1000],{},"getTenantId()"," を持ち、これがマルチテナントのキャッシュ分離を担保しています。",[573,1003,1004,1010],{},[244,1005,1006,1007,1009],{},"参照系の戻り値は ",[238,1008,672],{}," で返します。"," composable の外から状態を直接書き換えられると、TanStack Query のキャッシュと実際の表示がずれるからです。",[573,1012,1013,1016,1017,1020,1021,600,1024,1027],{},[244,1014,1015],{},"mutation の後はキャッシュを無効化します。"," ",[238,1018,1019],{},"queryKeys.tags.all()"," のような上位階層を invalidate すると、そこから派生する ",[238,1022,1023],{},"list()",[238,1025,1026],{},"roots()"," などが芋づる式に無効化されます。",[573,1029,1030,589,1032,1035,1036,1038],{},[238,1031,777],{},[238,1033,1034],{},"watch"," より優先します。",[238,1037,1034],{}," を使うのは API トリガー・DOM 操作・ステータス変化時のトースト通知など、副作用が本当に必要な場合だけです。",[253,1040,1042],{"id":1041},"component-層","component 層",[227,1044,1045,1046,1049],{},"component は composable だけを呼びます。",[238,1047,1048],{},"TagPicker.vue"," の例:",[231,1051,1055],{"className":1052,"code":1053,"language":1054,"meta":240,"style":240},"language-vue shiki shiki-themes github-light github-dark","\u003Cscript setup lang=\"ts\">\nconst { tags, createTag, deleteTag } = useTagManagement()\n\nconst tagOptions = computed\u003CMultiComboboxOption[]>(() =>\n  tags.value.map(tag => ({ value: tag.id!, label: tag.name! }))\n)\n\nconst handleCreate = async (query: string) => {\n  try {\n    const newTag = await createTag({ name: query })\n    // ...\n  } catch {\n    tagNotifications.notifyTagCreateError()\n  }\n}\n\u003C/script>\n","vue",[238,1056,1057,1079,1106,1110,1132,1160,1165,1169,1195,1202,1218,1223,1233,1243,1247,1251],{"__ignoreMap":240},[278,1058,1059,1061,1065,1068,1071,1073,1076],{"class":280,"line":281},[278,1060,366],{"class":305},[278,1062,1064],{"class":1063},"s9eBZ","script",[278,1066,1067],{"class":298}," setup",[278,1069,1070],{"class":298}," lang",[278,1072,763],{"class":305},[278,1074,1075],{"class":409},"\"ts\"",[278,1077,1078],{"class":305},">\n",[278,1080,1081,1084,1086,1089,1091,1093,1095,1098,1100,1102,1104],{"class":280,"line":288},[278,1082,1083],{"class":291},"const",[278,1085,742],{"class":305},[278,1087,1088],{"class":327},"tags",[278,1090,754],{"class":305},[278,1092,894],{"class":327},[278,1094,754],{"class":305},[278,1096,1097],{"class":327},"deleteTag",[278,1099,760],{"class":305},[278,1101,763],{"class":291},[278,1103,701],{"class":298},[278,1105,336],{"class":305},[278,1107,1108],{"class":280,"line":321},[278,1109,343],{"emptyLinePlaceholder":342},[278,1111,1112,1114,1117,1119,1121,1123,1126,1129],{"class":280,"line":339},[278,1113,1083],{"class":291},[278,1115,1116],{"class":327}," tagOptions",[278,1118,302],{"class":291},[278,1120,831],{"class":298},[278,1122,366],{"class":305},[278,1124,1125],{"class":298},"MultiComboboxOption",[278,1127,1128],{"class":305},"[]>(() ",[278,1130,1131],{"class":291},"=>\n",[278,1133,1134,1137,1140,1142,1144,1146,1149,1152,1155,1157],{"class":280,"line":346},[278,1135,1136],{"class":305},"  tags.value.",[278,1138,1139],{"class":298},"map",[278,1141,406],{"class":305},[278,1143,268],{"class":460},[278,1145,315],{"class":291},[278,1147,1148],{"class":305}," ({ value: tag.id",[278,1150,1151],{"class":291},"!",[278,1153,1154],{"class":305},", label: tag.name",[278,1156,1151],{"class":291},[278,1158,1159],{"class":305}," }))\n",[278,1161,1162],{"class":280,"line":380},[278,1163,1164],{"class":305},")\n",[278,1166,1167],{"class":280,"line":392},[278,1168,343],{"emptyLinePlaceholder":342},[278,1170,1171,1173,1176,1178,1180,1182,1185,1187,1189,1191,1193],{"class":280,"line":416},[278,1172,1083],{"class":291},[278,1174,1175],{"class":298}," handleCreate",[278,1177,302],{"class":291},[278,1179,356],{"class":291},[278,1181,457],{"class":305},[278,1183,1184],{"class":460},"query",[278,1186,309],{"class":291},[278,1188,466],{"class":327},[278,1190,872],{"class":305},[278,1192,375],{"class":291},[278,1194,318],{"class":305},[278,1196,1197,1200],{"class":280,"line":428},[278,1198,1199],{"class":291},"  try",[278,1201,318],{"class":305},[278,1203,1204,1206,1209,1211,1213,1215],{"class":280,"line":434},[278,1205,881],{"class":291},[278,1207,1208],{"class":327}," newTag",[278,1210,302],{"class":291},[278,1212,889],{"class":291},[278,1214,855],{"class":298},[278,1216,1217],{"class":305},"({ name: query })\n",[278,1219,1220],{"class":280,"line":440},[278,1221,1222],{"class":284},"    // ...\n",[278,1224,1225,1228,1231],{"class":280,"line":445},[278,1226,1227],{"class":305},"  } ",[278,1229,1230],{"class":291},"catch",[278,1232,318],{"class":305},[278,1234,1235,1238,1241],{"class":280,"line":488},[278,1236,1237],{"class":305},"    tagNotifications.",[278,1239,1240],{"class":298},"notifyTagCreateError",[278,1242,336],{"class":305},[278,1244,1245],{"class":280,"line":499},[278,1246,437],{"class":305},[278,1248,1249],{"class":280,"line":519},[278,1250,568],{"class":305},[278,1252,1253,1256,1258],{"class":280,"line":530},[278,1254,1255],{"class":305},"\u003C/",[278,1257,1064],{"class":1063},[278,1259,1078],{"class":305},[227,1261,1262,1263,1266,1267,1269],{},"component が ",[238,1264,1265],{},"useTagRepository()"," や ",[238,1268,249],{}," を直接 import することはありません。この一本道のおかげで、component は「どの composable を呼ぶか」だけを気にすればよく、API のエラーハンドリングやキャッシュ管理をコンポーネントごとに再発明せずに済みます。",[253,1271,1273],{"id":1272},"openapi-型生成","OpenAPI 型生成",[227,1275,1276,600,1279,600,1282,633,1285,1288],{},[238,1277,1278],{},"api.d.ts",[238,1280,1281],{},"openapi.json",[238,1283,1284],{},"zod-client.ts",[244,1286,1287],{},"手で編集しないファイル"," です。backend が公開する OpenAPI スキーマから自動生成されます。",[231,1290,1294],{"className":1291,"code":1292,"language":1293,"meta":240,"style":240},"language-bash shiki shiki-themes github-light github-dark","bun run openapi:all   # 独立した backend に対してのみ実行すること\n","bash",[238,1295,1296],{"__ignoreMap":240},[278,1297,1298,1301,1304,1307],{"class":280,"line":281},[278,1299,1300],{"class":298},"bun",[278,1302,1303],{"class":409}," run",[278,1305,1306],{"class":409}," openapi:all",[278,1308,1309],{"class":284},"   # 独立した backend に対してのみ実行すること\n",[227,1311,1312,1315,1316,1319,1320,1323,1324,1326,1327,577,1330,1333,1334,1337,1338,577,1341,1333,1344,1347,1348,1351],{},[238,1313,1314],{},"openapi:all"," は次を連続実行します: ",[238,1317,1318],{},"openapi:fetch","（backend の ",[238,1321,1322],{},"/v3/api-docs"," から ",[238,1325,1281],{}," を取得）→ ",[238,1328,1329],{},"openapi:generate",[238,1331,1332],{},"openapi-typescript"," で ",[238,1335,1336],{},"app/types/api.d.ts"," を生成）→ ",[238,1339,1340],{},"openapi:zod",[238,1342,1343],{},"openapi-zod-client",[238,1345,1346],{},"app/shared/api/zod-client.ts"," を生成、",[238,1349,1350],{},"schemas.*"," として Zod スキーマも書き出す）→ 各種プロパティ型/SSEストリーム型の再生成。",[1353,1354,1356],"callout",{"type":1355},"warning",[227,1357,1358,1359,1362,1363,1366],{},"オプションを付けずに ",[238,1360,1361],{},"bun run openapi:all"," を叩くと、デフォルトで ",[238,1364,1365],{},":8080"," の backend を見に行きます。並行して worktree を使っている場合、他のエージェントの未コミット WIP がそのまま frontend の型として焼き付いてしまいます。worktree で作業しているときは、自分の worktree の独立した backend に対して実行してください。",[227,1368,1369,1370,1373,1374,1376],{},"repository の型（Tier 1）は必ず ",[238,1371,1372],{},"components['schemas'][...]","（= ",[238,1375,1278],{}," 由来）から取ります。手書きの interface で代替すると OpenAPI と乖離します。",[1378,1379,1381],"h3",{"id":1380},"zod-による実行時検証","Zod による実行時検証",[227,1383,1384,1386,1387,1390,1391,577,1393,1395],{},[238,1385,1278],{}," の型はコンパイル時のみのチェックです。次のような ",[244,1388,1389],{},"信頼境界"," では、生成済みの ",[238,1392,1350],{},[238,1394,1284],{}," 由来）を使った実行時検証が必須です。",[570,1397,1398,1401,1404,1414,1417,1423],{},[573,1399,1400],{},"金銭・課金に関わるデータ",[573,1402,1403],{},"認証トークン",[573,1405,1406,1409,1410,1413],{},[238,1407,1408],{},"JSON.parse(localStorage)"," の結果（",[238,1411,1412],{},"readStoredJson"," を使う）",[573,1415,1416],{},"SSE / postMessage / ドラッグ&ドロップのペイロード",[573,1418,1419,1420,1422],{},"生の ",[238,1421,588],{}," の戻り値",[573,1424,1425,1426,1428],{},"まだ ",[238,1427,1278],{}," に載っていないエンドポイント",[231,1430,1432],{"className":272,"code":1431,"language":274,"meta":240,"style":240},"apiRequest(() => api.GET('/api/v1/tags'), { schema: schemas.TagResponseList })\n",[238,1433,1434],{"__ignoreMap":240},[278,1435,1436,1439,1441,1443,1445,1447,1449,1451],{"class":280,"line":281},[278,1437,1438],{"class":298},"apiRequest",[278,1440,780],{"class":305},[278,1442,375],{"class":291},[278,1444,400],{"class":305},[278,1446,403],{"class":298},[278,1448,406],{"class":305},[278,1450,410],{"class":409},[278,1452,1453],{"class":305},"), { schema: schemas.TagResponseList })\n",[227,1455,1456,1457,1460,1461,600,1464,1467],{},"それ以外の場所では、単純な ",[238,1458,1459],{},"as XResponse"," のキャストで問題ありません。ただし ",[238,1462,1463],{},"(api as any)",[238,1465,1466],{},"(api as unknown)"," は lint エラーです。",[253,1469,1471],{"id":1470},"mock-msw-との関係","mock / MSW との関係",[227,1473,1474,1475,1478,1479,1482],{},"テストと ",[238,1476,1477],{},"/__dev"," プレビューは実 backend を呼ばず、モックデータで動きます。3箇所を常に同期させる必要があります（",[244,1480,1481],{},"Mock 三点同期","）。",[1484,1485,1486,1493,1500],"ol",{},[573,1487,1488,1489,1492],{},"リポジトリルート ",[238,1490,1491],{},"shared/mock-data/"," — 正本の JSON データ",[573,1494,1495,1496,1499],{},"モジュールの mock repository（",[238,1497,1498],{},"TagRepository"," インターフェースを実装するフラットなファイル）",[573,1501,1502,1503,259,1506,1509],{},"MSW ハンドラ（",[238,1504,1505],{},"frontend/test/mocks/handlers/",[238,1507,1508],{},"server.ts","）",[227,1511,1512],{},"API のレスポンス形が変わったときは、この3箇所がずれていないか確認してください。ずれると「テストは通るのに実際のAPIとは形が違う」状態になります。",[253,1514,1515],{"id":1515},"次の一歩",[570,1517,1518,1524],{},[573,1519,1520,1523],{},[1521,1522,104],"a",{"href":105}," — API から取得したデータの表示テキストをどう組み立てるか",[573,1525,1526,1528],{},[1521,1527,108],{"href":109}," — component 層の見た目のルール",[1530,1531,1532],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":240,"searchDepth":288,"depth":288,"links":1534},[1535,1537,1539,1540,1543,1544],{"id":255,"depth":288,"text":1536},"repository 層 — useApi() + apiRequest()",{"id":668,"depth":288,"text":1538},"composable 層 — TanStack Query + readonly()",{"id":1041,"depth":288,"text":1042},{"id":1272,"depth":288,"text":1273,"children":1541},[1542],{"id":1380,"depth":321,"text":1381},{"id":1470,"depth":288,"text":1471},{"id":1515,"depth":288,"text":1515},"Component → composable (TanStack Query) → repository (useApi/apiRequest) のチェーン。OpenAPI型生成とmock/MSWの関係。","md",{},{"title":100,"description":1545},"b7nRm70hzDz7Odu29zOYlSd0YOXw8-QVSaoJbXbaRLg",[1551,1553,1554,1556,1557,1558,1559,1560,1561,1562,1563,1564,1566,1567,1568,1569,1570,1572,1573,1574,1575,1576,1578,1580,1582,1583,1584,1585,1586,1587,1588,1589,1590,1591,1592,1593,1594,1595,1596,1597,1598,1600,1601,1602,1603],{"path":13,"title":1552},"Astar とは何か（5分で）",{"path":17,"title":16},{"path":6,"title":1555},"ようこそ",{"path":29,"title":28},{"path":33,"title":32},{"path":37,"title":36},{"path":21,"title":25},{"path":49,"title":48},{"path":53,"title":52},{"path":57,"title":56},{"path":61,"title":60},{"path":41,"title":1565},"システムアーキテクチャ",{"path":73,"title":72},{"path":77,"title":76},{"path":81,"title":80},{"path":85,"title":84},{"path":65,"title":1571},"バックエンドガイド",{"path":97,"title":96},{"path":101,"title":100},{"path":105,"title":104},{"path":109,"title":108},{"path":89,"title":1577},"フロントエンドガイド",{"path":121,"title":1579},"Go agent — NAS 同期エージェント",{"path":125,"title":1581},"astar CLI",{"path":113,"title":117},{"path":137,"title":136},{"path":141,"title":140},{"path":145,"title":144},{"path":149,"title":148},{"path":153,"title":152},{"path":157,"title":156},{"path":161,"title":160},{"path":165,"title":164},{"path":169,"title":168},{"path":173,"title":172},{"path":177,"title":176},{"path":181,"title":180},{"path":185,"title":184},{"path":189,"title":188},{"path":193,"title":192},{"path":129,"title":1599},"モジュール索引",{"path":205,"title":204},{"path":209,"title":208},{"path":197,"title":201},{"path":213,"title":1604},"docs/ 地図",1785452455093]