[{"data":1,"prerenderedAt":1479},["ShallowReactive",2],{"docs-sections":3,"docs-page-\u002Fdocs\u002Fnuxt-layer":123},[4,9,13,17,22,26,30,34,38,42,46,50,55,59,63,67,72,76,80,85,89,93,97,102,106,110,114,119],{"path":5,"title":6,"description":7,"group":8},"\u002Fdocs","Introduction","What tunables is, the problem it solves, and the principles it holds to.","Get started",{"path":10,"title":11,"description":12,"group":8},"\u002Fdocs\u002Fquickstart","Quickstart","Take one group of settings from constants to a screen with a history, in a Nuxt app on Postgres.",{"path":14,"title":15,"description":16,"group":8},"\u002Fdocs\u002Fpatterns","What it is for","The kinds of value tunables keeps — live limits, plans, per-workspace and per-person values, model choices, values that bound each other, costly switches — and the feature that does each.",{"path":18,"title":19,"description":20,"group":21},"\u002Fdocs\u002Fsettings-and-groups","Settings and groups","Declare each setting once, in a group edited beside what it affects, and read it with types.","Concepts",{"path":23,"title":24,"description":25,"group":21},"\u002Fdocs\u002Fkinds-and-units","Kinds and units","The eight kinds of setting, the built-in units, your own units, and how values are checked and read.",{"path":27,"title":28,"description":29,"group":21},"\u002Fdocs\u002Fchecks-and-guards","Checks and guards","Rules across settings, guards for what your app keeps, within for presets, confirmations for costly changes, and notes.",{"path":31,"title":32,"description":33,"group":21},"\u002Fdocs\u002Fhistory-and-restore","History and restore","Every change is a revision with who, when, why and each value before and after; restoring one runs the same checks as any save.",{"path":35,"title":36,"description":37,"group":21},"\u002Fdocs\u002Fpresets","Presets","Named, reusable configurations with one default per slot, assigned by reference and bounded by the settings.",{"path":39,"title":40,"description":41,"group":21},"\u002Fdocs\u002Fscopes","Scopes","Values per workspace and per person, through layers — the system, a plan, the workspace, the person — each bounded by what it inherits.",{"path":43,"title":44,"description":45,"group":21},"\u002Fdocs\u002Fcatalogs","Catalogs","Lists of allowed choices, such as permitted models, whose entries are retired rather than deleted and cannot be retired while in use.",{"path":47,"title":48,"description":49,"group":21},"\u002Fdocs\u002Fdeployment-values","Deployment values","Values a deployment sets in its environment and nobody tunes while the app runs, declared once and listed in .env.example.",{"path":51,"title":52,"description":53,"group":54},"\u002Fdocs\u002Fstorage","Storage","Postgres through your own client, the four tables and how to create them, joining your transactions, and the storage contract behind it.","Integrate",{"path":56,"title":57,"description":58,"group":54},"\u002Fdocs\u002Fserver","Server and access","Mount tunables' API as a fetch handler in Nitro, Hono, Bun or Deno, as Express middleware or as an oRPC router, with access decided by your app.",{"path":60,"title":61,"description":62,"group":54},"\u002Fdocs\u002Faccess","Who can do what","End users bound by values they never see, staff who tune groups, owners and members who change only their own scope, and the terminal.",{"path":64,"title":65,"description":66,"group":54},"\u002Fdocs\u002Ftesting","Testing","Your app's tunables in memory for its own tests, with values set where a test needs them, and how to test rules, guards and confirmations.",{"path":68,"title":69,"description":70,"group":71},"\u002Fdocs\u002Fnuxt-layer","The Nuxt layer","Ready screens for a Nuxt UI app — a group's form, All settings, history, presets, assignments and catalogs — placed beside what they affect.","Screens",{"path":73,"title":74,"description":75,"group":71},"\u002Fdocs\u002Fvue-composables","Vue composables","Headless composables for building tunables' screens in any Vue app, on any design system.",{"path":77,"title":78,"description":79,"group":71},"\u002Fdocs\u002Ftheming","Theming","The screens bring no colors, fonts or CSS of their own; they take your Nuxt UI theme.",{"path":81,"title":82,"description":83,"group":84},"\u002Fdocs\u002Fcli","The CLI","The tunables command, tunables.config.ts, and which commands need a database.","CLI",{"path":86,"title":87,"description":88,"group":84},"\u002Fdocs\u002Fcli-check","Check and generate","The commands that need no database — checking definitions, writing .env.example, and keeping explanations and defaults in step with a pdd-kit Product Model.",{"path":90,"title":91,"description":92,"group":84},"\u002Fdocs\u002Fcli-values","Read and change values","The commands that read and change stored values, through the same checks and history as the screens.",{"path":94,"title":95,"description":96,"group":84},"\u002Fdocs\u002Fcli-skills","Install skills","Install tunables' agent skills for Claude Code, Codex CLI, Cursor, Gemini CLI and GitHub Copilot, and keep them current.",{"path":98,"title":99,"description":100,"group":101},"\u002Fdocs\u002Fskills","Agent skills","Three skills that let a coding agent adopt tunables, define settings the right way and verify that every value has one home.","Skills",{"path":103,"title":104,"description":105,"group":101},"\u002Fdocs\u002Fskill-adopt","tunables-adopt","Bring tunables into an app that has none yet, from an inventory of every value to a wired, synced app with the old homes removed.",{"path":107,"title":108,"description":109,"group":101},"\u002Fdocs\u002Fskill-define","tunables-define","Add, change, rename or remove one value in an app that uses tunables, with its home, its checks, its reads and what happens to stored values.",{"path":111,"title":112,"description":113,"group":101},"\u002Fdocs\u002Fskill-verify","tunables-verify","Audit an app's tunables against their rules, report every finding with where it is and the rule it breaks, and fix only the findings you choose.",{"path":115,"title":116,"description":117,"group":118},"\u002Fdocs\u002Fapi","API reference","Every export of the tunables package, by the subpath it is imported from.","Reference",{"path":120,"title":121,"description":122,"group":118},"\u002Fdocs\u002Ftasklane-example","Tasklane, the example app","A todo app with workspaces and people as tunables — two groups, plans as a layer, a catalog of models, scopes, a rule, a confirmation, and the playground that runs it.",{"id":124,"title":69,"body":125,"description":70,"extension":1473,"group":71,"meta":1474,"navigation":1133,"order":1475,"path":68,"seo":1476,"stem":1477,"__hash__":1478},"docs\u002Fdocs\u002Fnuxt-layer.md",{"type":126,"value":127,"toc":1460},"minimark",[128,132,140,220,239,244,254,258,405,409,461,464,507,510,521,591,628,637,640,670,673,676,744,750,754,795,798,803,868,883,886,928,931,935,949,995,1002,1038,1411,1423,1427,1442,1446,1456],[129,130,69],"h1",{"id":131},"the-nuxt-layer",[133,134,135,139],"p",{},[136,137,138],"code",{},"tunables\u002Fnuxt"," is a Nuxt layer with the screens an administrator needs, built\non Nuxt UI. Extend it, and its components are available on every page of your\napp, in your app's own theme.",[141,142,148],"pre",{"className":143,"code":144,"filename":145,"language":146,"meta":147,"style":147},"language-ts shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","export default defineNuxtConfig({\n  extends: ['tunables\u002Fnuxt'],\n  modules: ['@nuxt\u002Fui']\n})\n","nuxt.config.ts","ts","",[136,149,150,170,194,214],{"__ignoreMap":147},[151,152,155,159,162,166],"span",{"class":153,"line":154},"line",1,[151,156,158],{"class":157},"sZ4mS","export",[151,160,161],{"class":157}," default",[151,163,165],{"class":164},"sOThN"," defineNuxtConfig",[151,167,169],{"class":168},"sp9ci","({\n",[151,171,173,177,180,183,186,189,191],{"class":153,"line":172},2,[151,174,176],{"class":175},"sVz71","  extends",[151,178,179],{"class":168},":",[151,181,182],{"class":168}," [",[151,184,185],{"class":168},"'",[151,187,138],{"class":188},"sjCzm",[151,190,185],{"class":168},[151,192,193],{"class":168},"],\n",[151,195,197,200,202,204,206,209,211],{"class":153,"line":196},3,[151,198,199],{"class":175},"  modules",[151,201,179],{"class":168},[151,203,182],{"class":168},[151,205,185],{"class":168},[151,207,208],{"class":188},"@nuxt\u002Fui",[151,210,185],{"class":168},[151,212,213],{"class":168},"]\n",[151,215,217],{"class":153,"line":216},4,[151,218,219],{"class":168},"})\n",[133,221,222,223,226,227,229,230,234,235,238],{},"It needs ",[136,224,225],{},"nuxt"," 4.4 or later and ",[136,228,208],{}," 4.9 or later in your app, and the\nAPI mounted in your server: see ",[231,232,57],"a",{"href":233},".\u002Fserver",". Tailwind has\nto see the layer's components to generate their classes; see\n",[231,236,78],{"href":237},".\u002Ftheming#let-tailwind-see-the-components",".",[240,241,243],"h2",{"id":242},"where-the-screens-go","Where the screens go",[133,245,246,247,250,251,238],{},"Put each group's form on the page beside what it affects, not on one settings\npage: limits on the admin's Limits page, a workspace's own settings on that\nworkspace's settings page. A group's ",[136,248,249],{},"place"," names that page, so All settings\ncan link to it. The screens never decide who may see them: guard each page\nwith your own route middleware, and the API with ",[136,252,253],{},"authorize",[240,255,257],{"id":256},"the-components","The components",[259,260,261,277],"table",{},[262,263,264],"thead",{},[265,266,267,271,274],"tr",{},[268,269,270],"th",{},"Component",[268,272,273],{},"Props",[268,275,276],{},"Shows",[278,279,280,303,316,331,346,369,384],"tbody",{},[265,281,282,288,300],{},[283,284,285],"td",{},[136,286,287],{},"TunablesGroupForm",[283,289,290,293,294,293,297],{},[136,291,292],{},"group",", ",[136,295,296],{},"scope?",[136,298,299],{},"history?",[283,301,302],{},"One group's form, for the system or one scope",[265,304,305,310,313],{},[283,306,307],{},[136,308,309],{},"TunablesOverview",[283,311,312],{},"none",[283,314,315],{},"All settings, read-only",[265,317,318,323,328],{},[283,319,320],{},[136,321,322],{},"TunablesHistory",[283,324,325],{},[136,326,327],{},"target",[283,329,330],{},"A target's revisions, with restore for groups",[265,332,333,338,343],{},[283,334,335],{},[136,336,337],{},"TunablesPresets",[283,339,340],{},[136,341,342],{},"set",[283,344,345],{},"A preset set, by slot",[265,347,348,353,366],{},[283,349,350],{},[136,351,352],{},"TunablesAssign",[283,354,355,293,357,293,360,293,363],{},[136,356,342],{},[136,358,359],{},"subject",[136,361,362],{},"slot",[136,364,365],{},"label?",[283,367,368],{},"The preset one subject follows for one slot",[265,370,371,376,381],{},[283,372,373],{},[136,374,375],{},"TunablesCatalog",[283,377,378],{},[136,379,380],{},"catalog",[283,382,383],{},"A catalog's entries",[265,385,386,391,402],{},[283,387,388],{},[136,389,390],{},"TunablesFieldHint",[283,392,393,293,396,293,399],{},[136,394,395],{},"label",[136,397,398],{},"help?",[136,400,401],{},"defaultDisplay?",[283,403,404],{},"The question mark beside a label, for your own forms",[406,407,287],"h3",{"id":408},"tunablesgroupform",[141,410,415],{"className":411,"code":412,"filename":413,"language":414,"meta":147,"style":147},"language-vue shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","\u003Ctemplate>\n  \u003CTunablesGroupForm group=\"limits\" \u002F>\n\u003C\u002Ftemplate>\n","app\u002Fpages\u002Fadmin\u002Flimits.vue","vue",[136,416,417,428,452],{"__ignoreMap":147},[151,418,419,422,425],{"class":153,"line":154},[151,420,421],{"class":168},"\u003C",[151,423,424],{"class":188},"template",[151,426,427],{"class":168},">\n",[151,429,430,433,435,438,441,444,447,449],{"class":153,"line":172},[151,431,432],{"class":168},"  \u003C",[151,434,287],{"class":188},[151,436,437],{"class":164}," group",[151,439,440],{"class":168},"=",[151,442,443],{"class":168},"\"",[151,445,446],{"class":188},"limits",[151,448,443],{"class":168},[151,450,451],{"class":168}," \u002F>\n",[151,453,454,457,459],{"class":153,"line":196},[151,455,456],{"class":168},"\u003C\u002F",[151,458,424],{"class":188},[151,460,427],{"class":168},[133,462,463],{},"One group, edited as a whole:",[465,466,467,471,479,487,490,493,500],"ul",{},[468,469,470],"li",{},"every field under its section, with a section menu when there are several;",[468,472,473,474,478],{},"each field's label, its explanation and default behind a question mark, what\nchanging it affects, and the group's ",[231,475,477],{"href":476},".\u002Fchecks-and-guards#notes","notes",";",[468,480,481,482,486],{},"the input its ",[231,483,485],{"href":484},".\u002Fkinds-and-units","kind"," calls for, a mark on each unsaved\nchange, and a way back to the default;",[468,488,489],{},"one bar for the unsaved changes, with a reason that is kept in the history,\nDiscard and Save;",[468,491,492],{},"each problem beside the field it is about, and the rest above the form;",[468,494,495,496,478],{},"a dialog for a change that asks to be ",[231,497,499],{"href":498},".\u002Fchecks-and-guards#confirmations-for-costly-changes","confirmed",[468,501,502,503,506],{},"the group's history in a side panel, with restore. Pass ",[136,504,505],{},":history=\"false\"","\nto leave it out.",[133,508,509],{},"Leaving the page with unsaved changes asks first, by link or by closing the\ntab.",[133,511,512,513,516,517,520],{},"Given a ",[136,514,515],{},"scope",", the form edits that ",[231,518,515],{"href":519},".\u002Fscopes","'s own values:",[141,522,525],{"className":411,"code":523,"filename":524,"language":414,"meta":147,"style":147},"\u003Ctemplate>\n  \u003CTunablesGroupForm group=\"limits\" :scope=\"{ workspace: 'acme' }\" \u002F>\n\u003C\u002Ftemplate>\n","app\u002Fpages\u002Fworkspace.vue",[136,526,527,535,583],{"__ignoreMap":147},[151,528,529,531,533],{"class":153,"line":154},[151,530,421],{"class":168},[151,532,424],{"class":188},[151,534,427],{"class":168},[151,536,537,539,541,543,545,547,549,551,554,556,558,560,563,566,568,571,574,576,579,581],{"class":153,"line":172},[151,538,432],{"class":168},[151,540,287],{"class":188},[151,542,437],{"class":164},[151,544,440],{"class":168},[151,546,443],{"class":168},[151,548,446],{"class":188},[151,550,443],{"class":168},[151,552,553],{"class":168}," :",[151,555,515],{"class":164},[151,557,440],{"class":168},[151,559,443],{"class":168},[151,561,562],{"class":168},"{",[151,564,565],{"class":175}," workspace",[151,567,179],{"class":168},[151,569,570],{"class":168}," '",[151,572,573],{"class":188},"acme",[151,575,185],{"class":168},[151,577,578],{"class":168}," }",[151,580,443],{"class":168},[151,582,451],{"class":168},[151,584,585,587,589],{"class":153,"line":196},[151,586,456],{"class":168},[151,588,424],{"class":188},[151,590,427],{"class":168},[465,592,593,600,603,618,625],{},[468,594,595,596,599],{},"a badge beside the title names the scope, as your ",[136,597,598],{},"scopeLabel"," reads it;",[468,601,602],{},"only the fields that scope may set are shown, or a line saying it may set\nnone;",[468,604,605,606,610,611,614,615,478],{},"each field says where its value comes from, ",[607,608,609],"em",{},"Inherited from Pro plan"," or\n",[607,612,613],{},"Set here · inherits 50 from Pro plan",", with its bound, ",[607,616,617],{},"At most 50",[468,619,620,621,624],{},"a field set here offers ",[607,622,623],{},"Use 50 from Pro plan"," in place of the way back to\nits default;",[468,626,627],{},"the history panel lists that scope's own revisions.",[133,629,630,631,633,634,238],{},"Your ",[136,632,253],{}," must let the person reach their scope, and read the\ndescription; see ",[231,635,61],{"href":636},".\u002Faccess#self-service-per-scope",[406,638,309],{"id":639},"tunablesoverview",[141,641,644],{"className":411,"code":642,"filename":643,"language":414,"meta":147,"style":147},"\u003Ctemplate>\n  \u003CTunablesOverview \u002F>\n\u003C\u002Ftemplate>\n","app\u002Fpages\u002Fadmin\u002Fsettings.vue",[136,645,646,654,662],{"__ignoreMap":147},[151,647,648,650,652],{"class":153,"line":154},[151,649,421],{"class":168},[151,651,424],{"class":188},[151,653,427],{"class":168},[151,655,656,658,660],{"class":153,"line":172},[151,657,432],{"class":168},[151,659,309],{"class":188},[151,661,451],{"class":168},[151,663,664,666,668],{"class":153,"line":196},[151,665,456],{"class":168},[151,667,424],{"class":188},[151,669,427],{"class":168},[133,671,672],{},"All settings on one page: every field of every group, its value beside its\ndefault, what it affects, and a link to where it is edited. Find a setting by\nname, filter by what it affects, or show only what differs from the defaults.\nIt is read-only; changes happen on each group's own form.",[406,674,322],{"id":675},"tunableshistory",[141,677,679],{"className":411,"code":678,"language":414,"meta":147,"style":147},"\u003CTunablesHistory :target=\"{ kind: 'preset', name: 'plans', id: 'pro' }\" \u002F>\n",[136,680,681],{"__ignoreMap":147},[151,682,683,685,687,689,691,693,695,697,700,702,704,707,709,712,715,717,719,722,724,726,729,731,733,736,738,740,742],{"class":153,"line":154},[151,684,421],{"class":168},[151,686,322],{"class":188},[151,688,553],{"class":168},[151,690,327],{"class":164},[151,692,440],{"class":168},[151,694,443],{"class":168},[151,696,562],{"class":168},[151,698,699],{"class":175}," kind",[151,701,179],{"class":168},[151,703,570],{"class":168},[151,705,706],{"class":188},"preset",[151,708,185],{"class":168},[151,710,711],{"class":168},",",[151,713,714],{"class":175}," name",[151,716,179],{"class":168},[151,718,570],{"class":168},[151,720,721],{"class":188},"plans",[151,723,185],{"class":168},[151,725,711],{"class":168},[151,727,728],{"class":175}," id",[151,730,179],{"class":168},[151,732,570],{"class":168},[151,734,735],{"class":188},"pro",[151,737,185],{"class":168},[151,739,578],{"class":168},[151,741,443],{"class":168},[151,743,451],{"class":168},[133,745,746,747,238],{},"Revisions newest first, a page at a time: who, when, why, and each value\nbefore and after, as it reads. A group's revisions can be restored, through\nthe same checks as a save. It emits ",[136,748,749],{},"restored",[406,751,753],{"id":752},"tunablespresets-and-tunablesassign","TunablesPresets and TunablesAssign",[141,755,758],{"className":411,"code":756,"filename":757,"language":414,"meta":147,"style":147},"\u003Ctemplate>\n  \u003CTunablesPresets set=\"plans\" \u002F>\n\u003C\u002Ftemplate>\n","app\u002Fpages\u002Fadmin\u002Fplans.vue",[136,759,760,768,787],{"__ignoreMap":147},[151,761,762,764,766],{"class":153,"line":154},[151,763,421],{"class":168},[151,765,424],{"class":188},[151,767,427],{"class":168},[151,769,770,772,774,777,779,781,783,785],{"class":153,"line":172},[151,771,432],{"class":168},[151,773,337],{"class":188},[151,775,776],{"class":164}," set",[151,778,440],{"class":168},[151,780,443],{"class":168},[151,782,721],{"class":188},[151,784,443],{"class":168},[151,786,451],{"class":168},[151,788,789,791,793],{"class":153,"line":196},[151,790,456],{"class":168},[151,792,424],{"class":188},[151,794,427],{"class":168},[133,796,797],{},"A preset set by slot: each preset, which is the default, how many subjects are\nassigned it, and its history; create, edit, make default and remove.",[133,799,800,802],{},[136,801,352],{}," goes where your app shows a subject, such as a workspace's\nrow in the admin's list of workspaces:",[141,804,806],{"className":411,"code":805,"language":414,"meta":147,"style":147},"\u003CTunablesAssign set=\"plans\" :subject=\"workspace.id\" slot=\"plan\" label=\"Plan\" \u002F>\n",[136,807,808],{"__ignoreMap":147},[151,809,810,812,814,816,818,820,822,824,826,828,830,832,835,837,840,842,845,847,849,852,854,857,859,861,864,866],{"class":153,"line":154},[151,811,421],{"class":168},[151,813,352],{"class":188},[151,815,776],{"class":164},[151,817,440],{"class":168},[151,819,443],{"class":168},[151,821,721],{"class":188},[151,823,443],{"class":168},[151,825,553],{"class":168},[151,827,359],{"class":164},[151,829,440],{"class":168},[151,831,443],{"class":168},[151,833,834],{"class":175},"workspace",[151,836,238],{"class":168},[151,838,839],{"class":175},"id",[151,841,443],{"class":168},[151,843,844],{"class":164}," slot",[151,846,440],{"class":168},[151,848,443],{"class":168},[151,850,851],{"class":188},"plan",[151,853,443],{"class":168},[151,855,856],{"class":164}," label",[151,858,440],{"class":168},[151,860,443],{"class":168},[151,862,863],{"class":188},"Plan",[151,865,443],{"class":168},[151,867,451],{"class":168},[133,869,870,871,874,875,878,879,882],{},"It offers ",[607,872,873],{},"Follow the default"," and every preset in the slot, and emits\n",[136,876,877],{},"changed"," with the key chosen, or ",[136,880,881],{},"null"," for the default.",[406,884,375],{"id":885},"tunablescatalog",[141,887,890],{"className":411,"code":888,"filename":889,"language":414,"meta":147,"style":147},"\u003Ctemplate>\n  \u003CTunablesCatalog catalog=\"models\" \u002F>\n\u003C\u002Ftemplate>\n","app\u002Fpages\u002Fadmin\u002Fmodels.vue",[136,891,892,900,920],{"__ignoreMap":147},[151,893,894,896,898],{"class":153,"line":154},[151,895,421],{"class":168},[151,897,424],{"class":188},[151,899,427],{"class":168},[151,901,902,904,906,909,911,913,916,918],{"class":153,"line":172},[151,903,432],{"class":168},[151,905,375],{"class":188},[151,907,908],{"class":164}," catalog",[151,910,440],{"class":168},[151,912,443],{"class":168},[151,914,915],{"class":188},"models",[151,917,443],{"class":168},[151,919,451],{"class":168},[151,921,922,924,926],{"class":153,"line":196},[151,923,456],{"class":168},[151,925,424],{"class":188},[151,927,427],{"class":168},[133,929,930],{},"Every entry with its state and what uses it; add, edit, retire and reinstate.\nAn entry in use cannot be retired, and the screen says by what.",[240,932,934],{"id":933},"another-client","Another client",[133,936,937,938,941,942,945,946,179],{},"The screens call the API through a client. By default the layer provides\n",[136,939,940],{},"fetchClient"," at ",[136,943,944],{},"appConfig.tunables.baseURL",", which is ",[136,947,948],{},"\u002Fapi\u002Ftunables",[141,950,953],{"className":143,"code":951,"filename":952,"language":146,"meta":147,"style":147},"export default defineAppConfig({\n  tunables: { baseURL: '\u002Fadmin\u002Fapi\u002Ftunables' }\n})\n","app\u002Fapp.config.ts",[136,954,955,966,991],{"__ignoreMap":147},[151,956,957,959,961,964],{"class":153,"line":154},[151,958,158],{"class":157},[151,960,161],{"class":157},[151,962,963],{"class":164}," defineAppConfig",[151,965,169],{"class":168},[151,967,968,971,973,976,979,981,983,986,988],{"class":153,"line":172},[151,969,970],{"class":175},"  tunables",[151,972,179],{"class":168},[151,974,975],{"class":168}," {",[151,977,978],{"class":175}," baseURL",[151,980,179],{"class":168},[151,982,570],{"class":168},[151,984,985],{"class":188},"\u002Fadmin\u002Fapi\u002Ftunables",[151,987,185],{"class":168},[151,989,990],{"class":168}," }\n",[151,992,993],{"class":153,"line":196},[151,994,219],{"class":168},[133,996,997,998,1001],{},"To call tunables through your oRPC router instead, set ",[136,999,1000],{},"baseURL: false"," and\nprovide the client from a plugin of your own:",[141,1003,1005],{"className":143,"code":1004,"filename":952,"language":146,"meta":147,"style":147},"export default defineAppConfig({\n  tunables: { baseURL: false }\n})\n",[136,1006,1007,1017,1034],{"__ignoreMap":147},[151,1008,1009,1011,1013,1015],{"class":153,"line":154},[151,1010,158],{"class":157},[151,1012,161],{"class":157},[151,1014,963],{"class":164},[151,1016,169],{"class":168},[151,1018,1019,1021,1023,1025,1027,1029,1032],{"class":153,"line":172},[151,1020,970],{"class":175},[151,1022,179],{"class":168},[151,1024,975],{"class":168},[151,1026,978],{"class":175},[151,1028,179],{"class":168},[151,1030,1031],{"class":157}," false",[151,1033,990],{"class":168},[151,1035,1036],{"class":153,"line":196},[151,1037,219],{"class":168},[141,1039,1042],{"className":143,"code":1040,"filename":1041,"language":146,"meta":147,"style":147},"import { createORPCClient } from '@orpc\u002Fclient'\nimport { RPCLink } from '@orpc\u002Fclient\u002Ffetch'\nimport { orpcClient } from 'tunables\u002Fclient'\nimport { TUNABLES_CLIENT } from 'tunables\u002Fvue'\n\nexport default defineNuxtPlugin((nuxtApp) => {\n  const origin = import.meta.server ? useRequestURL().origin : window.location.origin\n  const link = new RPCLink({ url: `${origin}\u002Frpc`, headers: () => import.meta.server ? useRequestHeaders(['cookie']) : {} })\n  const client = createORPCClient\u003C{ tunables: Record\u003Cstring, (input?: unknown) => Promise\u003Cunknown>> }>(link)\n  nuxtApp.vueApp.provide(TUNABLES_CLIENT, orpcClient(client.tunables))\n})\n","app\u002Fplugins\u002Ftunables.ts",[136,1043,1044,1068,1088,1108,1128,1135,1160,1211,1297,1364,1406],{"__ignoreMap":147},[151,1045,1046,1049,1051,1054,1057,1060,1062,1065],{"class":153,"line":154},[151,1047,1048],{"class":157},"import",[151,1050,975],{"class":168},[151,1052,1053],{"class":175}," createORPCClient ",[151,1055,1056],{"class":168},"}",[151,1058,1059],{"class":157}," from",[151,1061,570],{"class":168},[151,1063,1064],{"class":188},"@orpc\u002Fclient",[151,1066,1067],{"class":168},"'\n",[151,1069,1070,1072,1074,1077,1079,1081,1083,1086],{"class":153,"line":172},[151,1071,1048],{"class":157},[151,1073,975],{"class":168},[151,1075,1076],{"class":175}," RPCLink ",[151,1078,1056],{"class":168},[151,1080,1059],{"class":157},[151,1082,570],{"class":168},[151,1084,1085],{"class":188},"@orpc\u002Fclient\u002Ffetch",[151,1087,1067],{"class":168},[151,1089,1090,1092,1094,1097,1099,1101,1103,1106],{"class":153,"line":196},[151,1091,1048],{"class":157},[151,1093,975],{"class":168},[151,1095,1096],{"class":175}," orpcClient ",[151,1098,1056],{"class":168},[151,1100,1059],{"class":157},[151,1102,570],{"class":168},[151,1104,1105],{"class":188},"tunables\u002Fclient",[151,1107,1067],{"class":168},[151,1109,1110,1112,1114,1117,1119,1121,1123,1126],{"class":153,"line":216},[151,1111,1048],{"class":157},[151,1113,975],{"class":168},[151,1115,1116],{"class":175}," TUNABLES_CLIENT ",[151,1118,1056],{"class":168},[151,1120,1059],{"class":157},[151,1122,570],{"class":168},[151,1124,1125],{"class":188},"tunables\u002Fvue",[151,1127,1067],{"class":168},[151,1129,1131],{"class":153,"line":1130},5,[151,1132,1134],{"emptyLinePlaceholder":1133},true,"\n",[151,1136,1138,1140,1142,1145,1148,1151,1154,1157],{"class":153,"line":1137},6,[151,1139,158],{"class":157},[151,1141,161],{"class":157},[151,1143,1144],{"class":164}," defineNuxtPlugin",[151,1146,1147],{"class":168},"((",[151,1149,1150],{"class":175},"nuxtApp",[151,1152,1153],{"class":168},")",[151,1155,1156],{"class":157}," =>",[151,1158,1159],{"class":168}," {\n",[151,1161,1163,1166,1169,1171,1174,1176,1179,1181,1184,1187,1190,1193,1196,1198,1201,1203,1206,1208],{"class":153,"line":1162},7,[151,1164,1165],{"class":157},"  const",[151,1167,1168],{"class":175}," origin ",[151,1170,440],{"class":168},[151,1172,1173],{"class":157}," import",[151,1175,238],{"class":168},[151,1177,1178],{"class":175},"meta",[151,1180,238],{"class":168},[151,1182,1183],{"class":175},"server ",[151,1185,1186],{"class":168},"?",[151,1188,1189],{"class":164}," useRequestURL",[151,1191,1192],{"class":168},"().",[151,1194,1195],{"class":175},"origin ",[151,1197,179],{"class":168},[151,1199,1200],{"class":175}," window",[151,1202,238],{"class":168},[151,1204,1205],{"class":175},"location",[151,1207,238],{"class":168},[151,1209,1210],{"class":175},"origin\n",[151,1212,1214,1216,1219,1221,1224,1227,1230,1233,1235,1238,1241,1243,1246,1249,1251,1254,1256,1259,1261,1263,1265,1267,1269,1271,1273,1276,1279,1281,1284,1286,1289,1291,1294],{"class":153,"line":1213},8,[151,1215,1165],{"class":157},[151,1217,1218],{"class":175}," link ",[151,1220,440],{"class":168},[151,1222,1223],{"class":157}," new",[151,1225,1226],{"class":164}," RPCLink",[151,1228,1229],{"class":168},"({",[151,1231,1232],{"class":175}," url",[151,1234,179],{"class":168},[151,1236,1237],{"class":168}," `${",[151,1239,1240],{"class":175},"origin",[151,1242,1056],{"class":168},[151,1244,1245],{"class":188},"\u002Frpc",[151,1247,1248],{"class":168},"`",[151,1250,711],{"class":168},[151,1252,1253],{"class":164}," headers",[151,1255,179],{"class":168},[151,1257,1258],{"class":168}," ()",[151,1260,1156],{"class":157},[151,1262,1173],{"class":157},[151,1264,238],{"class":168},[151,1266,1178],{"class":175},[151,1268,238],{"class":168},[151,1270,1183],{"class":175},[151,1272,1186],{"class":168},[151,1274,1275],{"class":164}," useRequestHeaders",[151,1277,1278],{"class":168},"([",[151,1280,185],{"class":168},[151,1282,1283],{"class":188},"cookie",[151,1285,185],{"class":168},[151,1287,1288],{"class":168},"])",[151,1290,553],{"class":168},[151,1292,1293],{"class":168}," {}",[151,1295,1296],{"class":168}," })\n",[151,1298,1300,1302,1305,1307,1310,1313,1316,1318,1321,1323,1326,1328,1331,1334,1337,1340,1342,1344,1347,1349,1352,1355,1358,1361],{"class":153,"line":1299},9,[151,1301,1165],{"class":157},[151,1303,1304],{"class":175}," client ",[151,1306,440],{"class":168},[151,1308,1309],{"class":164}," createORPCClient",[151,1311,1312],{"class":168},"\u003C{",[151,1314,1315],{"class":175}," tunables",[151,1317,179],{"class":168},[151,1319,1320],{"class":164}," Record",[151,1322,421],{"class":168},[151,1324,1325],{"class":164},"string",[151,1327,711],{"class":168},[151,1329,1330],{"class":168}," (",[151,1332,1333],{"class":175},"input",[151,1335,1336],{"class":168},"?:",[151,1338,1339],{"class":164}," unknown",[151,1341,1153],{"class":168},[151,1343,1156],{"class":157},[151,1345,1346],{"class":164}," Promise",[151,1348,421],{"class":168},[151,1350,1351],{"class":164},"unknown",[151,1353,1354],{"class":168},">>",[151,1356,1357],{"class":168}," }>(",[151,1359,1360],{"class":175},"link",[151,1362,1363],{"class":168},")\n",[151,1365,1367,1371,1374,1377,1379,1382,1385,1388,1390,1393,1395,1398,1400,1403],{"class":153,"line":1366},10,[151,1368,1370],{"class":1369},"sq0K3","  nuxtApp",[151,1372,238],{"class":1373},"sBd0f",[151,1375,1376],{"class":1369},"vueApp",[151,1378,238],{"class":1373},[151,1380,1381],{"class":164},"provide",[151,1383,1384],{"class":168},"(",[151,1386,1387],{"class":175},"TUNABLES_CLIENT",[151,1389,711],{"class":168},[151,1391,1392],{"class":164}," orpcClient",[151,1394,1384],{"class":168},[151,1396,1397],{"class":175},"client",[151,1399,238],{"class":168},[151,1401,1402],{"class":175},"tunables",[151,1404,1405],{"class":168},"))\n",[151,1407,1409],{"class":153,"line":1408},11,[151,1410,219],{"class":168},[133,1412,1413,1416,1417,1419,1420,238],{},[136,1414,1415],{},"orpcClient"," takes the ",[136,1418,1402],{}," branch of your oRPC client, wherever you\nnested ",[136,1421,1422],{},"tunablesRouter",[240,1424,1426],{"id":1425},"see-them-running","See them running",[133,1428,1429,1430,1433,1434,1438,1439,238],{},"The repository's ",[136,1431,1432],{},"playground\u002F"," is ",[231,1435,1437],{"href":1436},".\u002Ftasklane-example#the-playground","Tasklane","\non these components, with staff, a workspace owner and a member to switch\nbetween: ",[136,1440,1441],{},"pnpm --dir playground dev",[240,1443,1445],{"id":1444},"your-own-screens","Your own screens",[133,1447,1448,1449,1452,1453,1455],{},"The components are built on the ",[231,1450,74],{"href":1451},".\u002Fvue-composables",". When a\nscreen needs to look different, build it on those, and keep\n",[136,1454,390],{}," for the explanation beside each label.",[1457,1458,1459],"style",{},"html pre.shiki code .sZ4mS, html code.shiki .sZ4mS{--shiki-light:#115E59;--shiki-default:#115E59;--shiki-dark:#5EEAD4}html pre.shiki code .sOThN, html code.shiki .sOThN{--shiki-light:#2A3439;--shiki-light-font-weight:bold;--shiki-default:#2A3439;--shiki-default-font-weight:bold;--shiki-dark:#F6F8F8;--shiki-dark-font-weight:bold}html pre.shiki code .sp9ci, html code.shiki .sp9ci{--shiki-light:#4F5D63;--shiki-default:#4F5D63;--shiki-dark:#A3AFB4}html pre.shiki code .sVz71, html code.shiki .sVz71{--shiki-light:#182025;--shiki-default:#182025;--shiki-dark:#E6EBEC}html pre.shiki code .sjCzm, html code.shiki .sjCzm{--shiki-light:#92400E;--shiki-default:#92400E;--shiki-dark:#FCD34D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .sq0K3, html code.shiki .sq0K3{--shiki-light:#182025;--shiki-light-font-weight:bold;--shiki-default:#182025;--shiki-default-font-weight:bold;--shiki-dark:#E6EBEC;--shiki-dark-font-weight:bold}html pre.shiki code .sBd0f, html code.shiki .sBd0f{--shiki-light:#4F5D63;--shiki-light-font-weight:bold;--shiki-default:#4F5D63;--shiki-default-font-weight:bold;--shiki-dark:#A3AFB4;--shiki-dark-font-weight:bold}",{"title":147,"searchDepth":172,"depth":172,"links":1461},[1462,1463,1470,1471,1472],{"id":242,"depth":172,"text":243},{"id":256,"depth":172,"text":257,"children":1464},[1465,1466,1467,1468,1469],{"id":408,"depth":196,"text":287},{"id":639,"depth":196,"text":309},{"id":675,"depth":196,"text":322},{"id":752,"depth":196,"text":753},{"id":885,"depth":196,"text":375},{"id":933,"depth":172,"text":934},{"id":1425,"depth":172,"text":1426},{"id":1444,"depth":172,"text":1445},"md",{},16,{"title":69,"description":70},"docs\u002Fnuxt-layer","PfoX1O4EoOuP_l8lWhwuNsQZw9eRLD0_edWM9NGro_M",1792845117974]