[{"data":1,"prerenderedAt":806},["ShallowReactive",2],{"docs-sections":3,"docs-page-\u002Fdocs\u002Fcli-check":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":87,"body":125,"description":88,"extension":646,"group":84,"meta":801,"navigation":431,"order":802,"path":86,"seo":803,"stem":804,"__hash__":805},"docs\u002Fdocs\u002Fcli-check.md",{"type":126,"value":127,"toc":793},"minimark",[128,133,137,141,167,170,213,221,232,235,262,278,281,306,309,384,398,606,614,619,641,706,735,739,773,779,789],[129,130,132],"h1",{"id":131},"check-and-generate-check-env-explain-model-draft","Check and generate: check, env, explain, model draft",[134,135,136],"p",{},"These commands read only your definitions, your files and your Product Model.\nThey need no database, so they run in CI, in a pre-commit hook and in an agent's\nsandbox.",[138,139,140],"h2",{"id":140},"check",[142,143,148],"pre",{"className":144,"code":145,"language":146,"meta":147,"style":147},"language-bash shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","npx tunables check\n","bash","",[149,150,151],"code",{"__ignoreMap":147},[152,153,156,160,164],"span",{"class":154,"line":155},"line",1,[152,157,159],{"class":158},"sOThN","npx",[152,161,163],{"class":162},"sjCzm"," tunables",[152,165,166],{"class":162}," check\n",[134,168,169],{},"Checks, without a database, everything that must hold:",[171,172,173,185,198,207],"ul",{},[174,175,176,180,181,184],"li",{},[177,178,179],"strong",{},"The definitions are sound",": no id defined twice, every unit and effect\ndeclared, every ",[149,182,183],{},"pick"," naming a catalog, every preset set with a slot, and\nevery group and preset default passing its own check.",[174,186,187,193,194,197],{},[177,188,189,192],{},[149,190,191],{},".env.example"," lists every deployment value"," as declared, when the config\nhas an ",[149,195,196],{},"environment",".",[174,199,200,203,204,197],{},[177,201,202],{},"The explanations module is up to date"," with the Product Model, when the\nconfig names ",[149,205,206],{},"explanations",[174,208,209,212],{},[177,210,211],{},"Every field agrees with the Product Model",", when there is one: the Entity\na group names exists, the fact each field names is one that Entity keeps,\nand a default the model states is the field's own.",[142,214,219],{"className":215,"code":217,"language":218},[216],"language-text","✓ 2 groups, 1 preset sets and 1 catalogs are defined soundly\n✓ env.example lists all 3 deployment values\n✓ explanations.ts matches the Product Model\n✗ model: limits.maxProjects defaults to 5, but Limits says \"Projects\" defaults to 3.\n\n1 problem.\n","text",[149,220,217],{"__ignoreMap":147},[134,222,223,224,227,228,231],{},"It exits 1 when anything fails. ",[149,225,226],{},"--json"," writes\n",[149,229,230],{},"{ config, passed, findings }"," instead, for tools and agents.",[138,233,234],{"id":234},"env",[142,236,238],{"className":144,"code":237,"language":146,"meta":147,"style":147},"npx tunables env\nnpx tunables env --check\n",[149,239,240,249],{"__ignoreMap":147},[152,241,242,244,246],{"class":154,"line":155},[152,243,159],{"class":158},[152,245,163],{"class":162},[152,247,248],{"class":162}," env\n",[152,250,252,254,256,259],{"class":154,"line":251},2,[152,253,159],{"class":158},[152,255,163],{"class":162},[152,257,258],{"class":162}," env",[152,260,261],{"class":162}," --check\n",[134,263,264,265,267,268,273,274,277],{},"Writes ",[149,266,191],{}," from your ",[269,270,272],"a",{"href":271},".\u002Fdeployment-values","deployment values",":\neach under its section and description, commented out at its default, or left\nempty when it must be set. It rewrites the whole file, so a variable you have\nnot declared is dropped. ",[149,275,276],{},"--check"," only says whether the file is up to date,\nand exits 1 when it is not.",[138,279,280],{"id":280},"explain",[142,282,284],{"className":144,"code":283,"language":146,"meta":147,"style":147},"npx tunables explain\nnpx tunables explain --check\n",[149,285,286,295],{"__ignoreMap":147},[152,287,288,290,292],{"class":154,"line":155},[152,289,159],{"class":158},[152,291,163],{"class":162},[152,293,294],{"class":162}," explain\n",[152,296,297,299,301,304],{"class":154,"line":251},[152,298,159],{"class":158},[152,300,163],{"class":162},[152,302,303],{"class":162}," explain",[152,305,261],{"class":162},[134,307,308],{},"Writes the explanations module from your Product Model, so each field explains\nitself in the model's own words. Name where it goes in the config:",[142,310,315],{"className":311,"code":312,"filename":313,"language":314,"meta":147,"style":147},"language-ts shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","export default defineConfig({\n  groups,\n  productModel: 'product-model',\n  explanations: 'server\u002Ftunables-explanations.ts'\n})\n","tunables.config.ts","ts",[149,316,317,333,342,362,378],{"__ignoreMap":147},[152,318,319,323,326,329],{"class":154,"line":155},[152,320,322],{"class":321},"sZ4mS","export",[152,324,325],{"class":321}," default",[152,327,328],{"class":158}," defineConfig",[152,330,332],{"class":331},"sp9ci","({\n",[152,334,335,339],{"class":154,"line":251},[152,336,338],{"class":337},"sVz71","  groups",[152,340,341],{"class":331},",\n",[152,343,345,348,351,354,357,360],{"class":154,"line":344},3,[152,346,347],{"class":337},"  productModel",[152,349,350],{"class":331},":",[152,352,353],{"class":331}," '",[152,355,356],{"class":162},"product-model",[152,358,359],{"class":331},"'",[152,361,341],{"class":331},[152,363,365,368,370,372,375],{"class":154,"line":364},4,[152,366,367],{"class":337},"  explanations",[152,369,350],{"class":331},[152,371,353],{"class":331},[152,373,374],{"class":162},"server\u002Ftunables-explanations.ts",[152,376,377],{"class":331},"'\n",[152,379,381],{"class":154,"line":380},5,[152,382,383],{"class":331},"})\n",[134,385,386,387,390,391,394,395,350],{},"Then pass it to ",[149,388,389],{},"createTunables",", and give fields a ",[149,392,393],{},"fact"," instead of ",[149,396,397],{},"help",[142,399,401],{"className":311,"code":400,"language":314,"meta":147,"style":147},"import { explanations } from '.\u002Ftunables-explanations'\n\nconst limits = defineGroup({\n  id: 'limits',\n  title: 'Limits',\n  entity: 'limits',\n  fields: {\n    maxProjects: number({ fact: 'Projects', unit: 'count', default: 3 })\n  }\n})\n\ncreateTunables({ store, groups: [limits], explanations })\n",[149,402,403,427,433,449,465,481,497,508,561,567,572,577],{"__ignoreMap":147},[152,404,405,408,411,414,417,420,422,425],{"class":154,"line":155},[152,406,407],{"class":321},"import",[152,409,410],{"class":331}," {",[152,412,413],{"class":337}," explanations ",[152,415,416],{"class":331},"}",[152,418,419],{"class":321}," from",[152,421,353],{"class":331},[152,423,424],{"class":162},".\u002Ftunables-explanations",[152,426,377],{"class":331},[152,428,429],{"class":154,"line":251},[152,430,432],{"emptyLinePlaceholder":431},true,"\n",[152,434,435,438,441,444,447],{"class":154,"line":344},[152,436,437],{"class":321},"const",[152,439,440],{"class":337}," limits ",[152,442,443],{"class":331},"=",[152,445,446],{"class":158}," defineGroup",[152,448,332],{"class":331},[152,450,451,454,456,458,461,463],{"class":154,"line":364},[152,452,453],{"class":337},"  id",[152,455,350],{"class":331},[152,457,353],{"class":331},[152,459,460],{"class":162},"limits",[152,462,359],{"class":331},[152,464,341],{"class":331},[152,466,467,470,472,474,477,479],{"class":154,"line":380},[152,468,469],{"class":337},"  title",[152,471,350],{"class":331},[152,473,353],{"class":331},[152,475,476],{"class":162},"Limits",[152,478,359],{"class":331},[152,480,341],{"class":331},[152,482,484,487,489,491,493,495],{"class":154,"line":483},6,[152,485,486],{"class":337},"  entity",[152,488,350],{"class":331},[152,490,353],{"class":331},[152,492,460],{"class":162},[152,494,359],{"class":331},[152,496,341],{"class":331},[152,498,500,503,505],{"class":154,"line":499},7,[152,501,502],{"class":337},"  fields",[152,504,350],{"class":331},[152,506,507],{"class":331}," {\n",[152,509,511,514,516,519,522,525,527,529,532,534,537,540,542,544,547,549,551,553,555,558],{"class":154,"line":510},8,[152,512,513],{"class":337},"    maxProjects",[152,515,350],{"class":331},[152,517,518],{"class":158}," number",[152,520,521],{"class":331},"({",[152,523,524],{"class":337}," fact",[152,526,350],{"class":331},[152,528,353],{"class":331},[152,530,531],{"class":162},"Projects",[152,533,359],{"class":331},[152,535,536],{"class":331},",",[152,538,539],{"class":337}," unit",[152,541,350],{"class":331},[152,543,353],{"class":331},[152,545,546],{"class":162},"count",[152,548,359],{"class":331},[152,550,536],{"class":331},[152,552,325],{"class":337},[152,554,350],{"class":331},[152,556,557],{"class":162}," 3",[152,559,560],{"class":331}," })\n",[152,562,564],{"class":154,"line":563},9,[152,565,566],{"class":331},"  }\n",[152,568,570],{"class":154,"line":569},10,[152,571,383],{"class":331},[152,573,575],{"class":154,"line":574},11,[152,576,432],{"emptyLinePlaceholder":431},[152,578,580,582,584,587,589,592,594,597,599,602,604],{"class":154,"line":579},12,[152,581,389],{"class":158},[152,583,521],{"class":331},[152,585,586],{"class":337}," store",[152,588,536],{"class":331},[152,590,591],{"class":337}," groups",[152,593,350],{"class":331},[152,595,596],{"class":331}," [",[152,598,460],{"class":337},[152,600,601],{"class":331},"],",[152,603,413],{"class":337},[152,605,383],{"class":331},[134,607,608,610,611,613],{},[149,609,276],{}," only says whether the module is current. Never edit the module by\nhand: change the model, then run ",[149,612,280],{}," again.",[615,616,618],"h3",{"id":617},"how-the-model-is-read","How the model is read",[134,620,621,622,628,629,632,633,636,637,640],{},"A ",[269,623,627],{"href":624,"rel":625},"https:\u002F\u002Fpdd-kit.dev",[626],"nofollow","pdd-kit"," Product Model keeps each Entity in\n",[149,630,631],{},"product-model\u002Fentities\u002F\u003Cid>.md",". A group's ",[149,634,635],{},"entity"," is that id; each line under\nthe Entity's ",[149,638,639],{},"## Information kept"," is a fact:",[142,642,647],{"className":643,"code":644,"filename":645,"language":646,"meta":147,"style":147},"language-md shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","# Limits\n\n## Information kept\n\n- **Projects** — the most projects a workspace may have open at once; defaults to 3\n- **Attachment size** — the largest file a task may carry; defaults to 10 megabytes\n","product-model\u002Fentities\u002Flimits.md","md",[149,648,649,659,663,671,675,692],{"__ignoreMap":147},[152,650,651,655],{"class":154,"line":155},[152,652,654],{"class":653},"sBd0f","#",[152,656,658],{"class":657},"s4VG2"," Limits\n",[152,660,661],{"class":154,"line":251},[152,662,432],{"emptyLinePlaceholder":431},[152,664,665,668],{"class":154,"line":344},[152,666,667],{"class":653},"##",[152,669,670],{"class":657}," Information kept\n",[152,672,673],{"class":154,"line":364},[152,674,432],{"emptyLinePlaceholder":431},[152,676,677,680,683,686,689],{"class":154,"line":380},[152,678,679],{"class":331},"-",[152,681,682],{"class":653}," **",[152,684,531],{"class":685},"sq0K3",[152,687,688],{"class":653},"**",[152,690,691],{"class":337}," — the most projects a workspace may have open at once; defaults to 3\n",[152,693,694,696,698,701,703],{"class":154,"line":483},[152,695,679],{"class":331},[152,697,682],{"class":653},[152,699,700],{"class":685},"Attachment size",[152,702,688],{"class":653},[152,704,705],{"class":337}," — the largest file a task may carry; defaults to 10 megabytes\n",[134,707,708,709,712,713,715,716,719,720,723,724,727,728,731,732,197],{},"The bold name is the fact a field names. The words after the dash are its\nexplanation; what follows ",[149,710,711],{},"defaults to"," is the default the model states, and\n",[149,714,140],{}," compares it with the field's own default as it reads, by its numbers:\n",[149,717,718],{},"10 megabytes"," agrees with ",[149,721,722],{},"10 MB",", and ",[149,725,726],{},"400 by 225 pixels"," with\n",[149,729,730],{},"400 × 225 pixels",". Without numbers, the words must match, such as\n",[149,733,734],{},"List",[138,736,738],{"id":737},"model-draft","model draft",[142,740,742],{"className":144,"code":741,"language":146,"meta":147,"style":147},"npx tunables model draft\nnpx tunables model draft --out \u002Ftmp\u002Fsettings-facts.md\n",[149,743,744,756],{"__ignoreMap":147},[152,745,746,748,750,753],{"class":154,"line":155},[152,747,159],{"class":158},[152,749,163],{"class":162},[152,751,752],{"class":162}," model",[152,754,755],{"class":162}," draft\n",[152,757,758,760,762,764,767,770],{"class":154,"line":251},[152,759,159],{"class":158},[152,761,163],{"class":162},[152,763,752],{"class":162},[152,765,766],{"class":162}," draft",[152,768,769],{"class":162}," --out",[152,771,772],{"class":162}," \u002Ftmp\u002Fsettings-facts.md\n",[134,774,775,776,197],{},"Drafts the facts the Product Model lacks: for each group, preset set and\ncatalog, one line in the model's own form for every field that names no fact\nthe model keeps, with its explanation and, for a group, its default. It prints\nthe draft, or writes it to ",[149,777,778],{},"--out",[134,780,781,782,785,786,197],{},"It never writes the model. A Product Model decides what the product keeps, so\nbring the draft to ",[149,783,784],{},"\u002Fpdd-ideate",", which decides each fact with you and writes\nit. Then run ",[149,787,788],{},"tunables explain",[790,791,792],"style",{},"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 .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 .sZ4mS, html code.shiki .sZ4mS{--shiki-light:#115E59;--shiki-default:#115E59;--shiki-dark:#5EEAD4}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 .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}html pre.shiki code .s4VG2, html code.shiki .s4VG2{--shiki-light:#115E59;--shiki-light-font-weight:bold;--shiki-default:#115E59;--shiki-default-font-weight:bold;--shiki-dark:#5EEAD4;--shiki-dark-font-weight:bold}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}",{"title":147,"searchDepth":251,"depth":251,"links":794},[795,796,797,800],{"id":140,"depth":251,"text":140},{"id":234,"depth":251,"text":234},{"id":280,"depth":251,"text":280,"children":798},[799],{"id":617,"depth":344,"text":618},{"id":737,"depth":251,"text":738},{},20,{"title":87,"description":88},"docs\u002Fcli-check","k3Q5odaaCKW1qwqHLos-JIYaU1zpJ5HDsZSqj7xq4wQ",1792845118092]