[{"data":1,"prerenderedAt":1946},["ShallowReactive",2],{"docs-sections":3,"docs-page-\u002Fdocs\u002Fsettings-and-groups":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":19,"body":125,"description":20,"extension":1941,"group":21,"meta":1942,"navigation":225,"order":250,"path":18,"seo":1943,"stem":1944,"__hash__":1945},"docs\u002Fdocs\u002Fsettings-and-groups.md",{"type":126,"value":127,"toc":1928},"minimark",[128,132,151,156,824,827,928,932,935,1022,1045,1050,1070,1074,1079,1083,1088,1189,1255,1258,1328,1331,1335,1365,1430,1434,1437,1624,1630,1697,1700,1773,1779,1856,1859,1911,1915,1924],[129,130,19],"h1",{"id":131},"settings-and-groups",[133,134,135,136,140,141,144,145,150],"p",{},"A ",[137,138,139],"strong",{},"setting"," is a value someone tunes while the app runs: a limit, an\ninterval, a budget, a switch. A ",[137,142,143],{},"group"," is a set of settings edited together,\non one form, beside what they affect. Every setting belongs to exactly one\ngroup. A group's values are stored as one row, with one more for each\nworkspace or person that ",[146,147,149],"a",{"href":148},".\u002Fscopes","sets its own",".",[152,153,155],"h2",{"id":154},"declaring-a-group","Declaring a group",[157,158,163],"pre",{"className":159,"code":160,"language":161,"meta":162,"style":162},"language-ts shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","import { defineEffects, defineGroup, number, range, toggle } from 'tunables'\n\nexport const effects = defineEffects({\n  members: { label: 'What everyone in the workspace sees', icon: 'i-lucide-users' },\n  cost: { label: 'AI cost', warn: true, icon: 'i-lucide-coins' }\n})\n\nexport const limits = defineGroup({\n  id: 'limits',\n  title: 'Limits',\n  description: 'How much a workspace may hold and use.',\n  place: { label: 'Admin › Limits', href: '\u002Fadmin\u002Flimits' },\n  fields: {\n    maxProjects: number({\n      label: 'Projects',\n      help: 'The most projects a workspace may have open at once.',\n      unit: 'count',\n      default: 3,\n      section: 'What a workspace holds',\n      affects: ['members']\n    }),\n    titleLength: range({\n      label: 'Task title length',\n      help: 'How short and how long a task\\'s title may be.',\n      unit: 'characters',\n      default: { min: 1, max: 200 },\n      section: 'What a workspace holds',\n      affects: ['members']\n    }),\n    aiSuggestions: toggle({\n      label: 'AI suggestions',\n      help: 'Whether people may ask for suggested tasks.',\n      default: true,\n      section: 'AI',\n      affects: ['members', 'cost']\n    })\n  }\n})\n","ts","",[164,165,166,220,227,248,289,336,342,347,363,381,398,415,452,463,475,492,509,526,539,556,577,583,595,611,627,643,672,687,704,709,722,738,754,765,781,807,813,819],"code",{"__ignoreMap":162},[167,168,171,175,179,183,186,189,191,194,196,199,201,204,207,210,213,217],"span",{"class":169,"line":170},"line",1,[167,172,174],{"class":173},"sZ4mS","import",[167,176,178],{"class":177},"sp9ci"," {",[167,180,182],{"class":181},"sVz71"," defineEffects",[167,184,185],{"class":177},",",[167,187,188],{"class":181}," defineGroup",[167,190,185],{"class":177},[167,192,193],{"class":181}," number",[167,195,185],{"class":177},[167,197,198],{"class":181}," range",[167,200,185],{"class":177},[167,202,203],{"class":181}," toggle ",[167,205,206],{"class":177},"}",[167,208,209],{"class":173}," from",[167,211,212],{"class":177}," '",[167,214,216],{"class":215},"sjCzm","tunables",[167,218,219],{"class":177},"'\n",[167,221,223],{"class":169,"line":222},2,[167,224,226],{"emptyLinePlaceholder":225},true,"\n",[167,228,230,233,236,239,242,245],{"class":169,"line":229},3,[167,231,232],{"class":173},"export",[167,234,235],{"class":173}," const",[167,237,238],{"class":181}," effects ",[167,240,241],{"class":177},"=",[167,243,182],{"class":244},"sOThN",[167,246,247],{"class":177},"({\n",[167,249,251,254,257,259,262,264,266,269,272,274,277,279,281,284,286],{"class":169,"line":250},4,[167,252,253],{"class":181},"  members",[167,255,256],{"class":177},":",[167,258,178],{"class":177},[167,260,261],{"class":181}," label",[167,263,256],{"class":177},[167,265,212],{"class":177},[167,267,268],{"class":215},"What everyone in the workspace sees",[167,270,271],{"class":177},"'",[167,273,185],{"class":177},[167,275,276],{"class":181}," icon",[167,278,256],{"class":177},[167,280,212],{"class":177},[167,282,283],{"class":215},"i-lucide-users",[167,285,271],{"class":177},[167,287,288],{"class":177}," },\n",[167,290,292,295,297,299,301,303,305,308,310,312,315,317,320,322,324,326,328,331,333],{"class":169,"line":291},5,[167,293,294],{"class":181},"  cost",[167,296,256],{"class":177},[167,298,178],{"class":177},[167,300,261],{"class":181},[167,302,256],{"class":177},[167,304,212],{"class":177},[167,306,307],{"class":215},"AI cost",[167,309,271],{"class":177},[167,311,185],{"class":177},[167,313,314],{"class":181}," warn",[167,316,256],{"class":177},[167,318,319],{"class":173}," true",[167,321,185],{"class":177},[167,323,276],{"class":181},[167,325,256],{"class":177},[167,327,212],{"class":177},[167,329,330],{"class":215},"i-lucide-coins",[167,332,271],{"class":177},[167,334,335],{"class":177}," }\n",[167,337,339],{"class":169,"line":338},6,[167,340,341],{"class":177},"})\n",[167,343,345],{"class":169,"line":344},7,[167,346,226],{"emptyLinePlaceholder":225},[167,348,350,352,354,357,359,361],{"class":169,"line":349},8,[167,351,232],{"class":173},[167,353,235],{"class":173},[167,355,356],{"class":181}," limits ",[167,358,241],{"class":177},[167,360,188],{"class":244},[167,362,247],{"class":177},[167,364,366,369,371,373,376,378],{"class":169,"line":365},9,[167,367,368],{"class":181},"  id",[167,370,256],{"class":177},[167,372,212],{"class":177},[167,374,375],{"class":215},"limits",[167,377,271],{"class":177},[167,379,380],{"class":177},",\n",[167,382,384,387,389,391,394,396],{"class":169,"line":383},10,[167,385,386],{"class":181},"  title",[167,388,256],{"class":177},[167,390,212],{"class":177},[167,392,393],{"class":215},"Limits",[167,395,271],{"class":177},[167,397,380],{"class":177},[167,399,401,404,406,408,411,413],{"class":169,"line":400},11,[167,402,403],{"class":181},"  description",[167,405,256],{"class":177},[167,407,212],{"class":177},[167,409,410],{"class":215},"How much a workspace may hold and use.",[167,412,271],{"class":177},[167,414,380],{"class":177},[167,416,418,421,423,425,427,429,431,434,436,438,441,443,445,448,450],{"class":169,"line":417},12,[167,419,420],{"class":181},"  place",[167,422,256],{"class":177},[167,424,178],{"class":177},[167,426,261],{"class":181},[167,428,256],{"class":177},[167,430,212],{"class":177},[167,432,433],{"class":215},"Admin › Limits",[167,435,271],{"class":177},[167,437,185],{"class":177},[167,439,440],{"class":181}," href",[167,442,256],{"class":177},[167,444,212],{"class":177},[167,446,447],{"class":215},"\u002Fadmin\u002Flimits",[167,449,271],{"class":177},[167,451,288],{"class":177},[167,453,455,458,460],{"class":169,"line":454},13,[167,456,457],{"class":181},"  fields",[167,459,256],{"class":177},[167,461,462],{"class":177}," {\n",[167,464,466,469,471,473],{"class":169,"line":465},14,[167,467,468],{"class":181},"    maxProjects",[167,470,256],{"class":177},[167,472,193],{"class":244},[167,474,247],{"class":177},[167,476,478,481,483,485,488,490],{"class":169,"line":477},15,[167,479,480],{"class":181},"      label",[167,482,256],{"class":177},[167,484,212],{"class":177},[167,486,487],{"class":215},"Projects",[167,489,271],{"class":177},[167,491,380],{"class":177},[167,493,495,498,500,502,505,507],{"class":169,"line":494},16,[167,496,497],{"class":181},"      help",[167,499,256],{"class":177},[167,501,212],{"class":177},[167,503,504],{"class":215},"The most projects a workspace may have open at once.",[167,506,271],{"class":177},[167,508,380],{"class":177},[167,510,512,515,517,519,522,524],{"class":169,"line":511},17,[167,513,514],{"class":181},"      unit",[167,516,256],{"class":177},[167,518,212],{"class":177},[167,520,521],{"class":215},"count",[167,523,271],{"class":177},[167,525,380],{"class":177},[167,527,529,532,534,537],{"class":169,"line":528},18,[167,530,531],{"class":181},"      default",[167,533,256],{"class":177},[167,535,536],{"class":215}," 3",[167,538,380],{"class":177},[167,540,542,545,547,549,552,554],{"class":169,"line":541},19,[167,543,544],{"class":181},"      section",[167,546,256],{"class":177},[167,548,212],{"class":177},[167,550,551],{"class":215},"What a workspace holds",[167,553,271],{"class":177},[167,555,380],{"class":177},[167,557,559,562,564,567,569,572,574],{"class":169,"line":558},20,[167,560,561],{"class":181},"      affects",[167,563,256],{"class":177},[167,565,566],{"class":177}," [",[167,568,271],{"class":177},[167,570,571],{"class":215},"members",[167,573,271],{"class":177},[167,575,576],{"class":177},"]\n",[167,578,580],{"class":169,"line":579},21,[167,581,582],{"class":177},"    }),\n",[167,584,586,589,591,593],{"class":169,"line":585},22,[167,587,588],{"class":181},"    titleLength",[167,590,256],{"class":177},[167,592,198],{"class":244},[167,594,247],{"class":177},[167,596,598,600,602,604,607,609],{"class":169,"line":597},23,[167,599,480],{"class":181},[167,601,256],{"class":177},[167,603,212],{"class":177},[167,605,606],{"class":215},"Task title length",[167,608,271],{"class":177},[167,610,380],{"class":177},[167,612,614,616,618,620,623,625],{"class":169,"line":613},24,[167,615,497],{"class":181},[167,617,256],{"class":177},[167,619,212],{"class":177},[167,621,622],{"class":215},"How short and how long a task\\'s title may be.",[167,624,271],{"class":177},[167,626,380],{"class":177},[167,628,630,632,634,636,639,641],{"class":169,"line":629},25,[167,631,514],{"class":181},[167,633,256],{"class":177},[167,635,212],{"class":177},[167,637,638],{"class":215},"characters",[167,640,271],{"class":177},[167,642,380],{"class":177},[167,644,646,648,650,652,655,657,660,662,665,667,670],{"class":169,"line":645},26,[167,647,531],{"class":181},[167,649,256],{"class":177},[167,651,178],{"class":177},[167,653,654],{"class":181}," min",[167,656,256],{"class":177},[167,658,659],{"class":215}," 1",[167,661,185],{"class":177},[167,663,664],{"class":181}," max",[167,666,256],{"class":177},[167,668,669],{"class":215}," 200",[167,671,288],{"class":177},[167,673,675,677,679,681,683,685],{"class":169,"line":674},27,[167,676,544],{"class":181},[167,678,256],{"class":177},[167,680,212],{"class":177},[167,682,551],{"class":215},[167,684,271],{"class":177},[167,686,380],{"class":177},[167,688,690,692,694,696,698,700,702],{"class":169,"line":689},28,[167,691,561],{"class":181},[167,693,256],{"class":177},[167,695,566],{"class":177},[167,697,271],{"class":177},[167,699,571],{"class":215},[167,701,271],{"class":177},[167,703,576],{"class":177},[167,705,707],{"class":169,"line":706},29,[167,708,582],{"class":177},[167,710,712,715,717,720],{"class":169,"line":711},30,[167,713,714],{"class":181},"    aiSuggestions",[167,716,256],{"class":177},[167,718,719],{"class":244}," toggle",[167,721,247],{"class":177},[167,723,725,727,729,731,734,736],{"class":169,"line":724},31,[167,726,480],{"class":181},[167,728,256],{"class":177},[167,730,212],{"class":177},[167,732,733],{"class":215},"AI suggestions",[167,735,271],{"class":177},[167,737,380],{"class":177},[167,739,741,743,745,747,750,752],{"class":169,"line":740},32,[167,742,497],{"class":181},[167,744,256],{"class":177},[167,746,212],{"class":177},[167,748,749],{"class":215},"Whether people may ask for suggested tasks.",[167,751,271],{"class":177},[167,753,380],{"class":177},[167,755,757,759,761,763],{"class":169,"line":756},33,[167,758,531],{"class":181},[167,760,256],{"class":177},[167,762,319],{"class":173},[167,764,380],{"class":177},[167,766,768,770,772,774,777,779],{"class":169,"line":767},34,[167,769,544],{"class":181},[167,771,256],{"class":177},[167,773,212],{"class":177},[167,775,776],{"class":215},"AI",[167,778,271],{"class":177},[167,780,380],{"class":177},[167,782,784,786,788,790,792,794,796,798,800,803,805],{"class":169,"line":783},35,[167,785,561],{"class":181},[167,787,256],{"class":177},[167,789,566],{"class":177},[167,791,271],{"class":177},[167,793,571],{"class":215},[167,795,271],{"class":177},[167,797,185],{"class":177},[167,799,212],{"class":177},[167,801,802],{"class":215},"cost",[167,804,271],{"class":177},[167,806,576],{"class":177},[167,808,810],{"class":169,"line":809},36,[167,811,812],{"class":177},"    })\n",[167,814,816],{"class":169,"line":815},37,[167,817,818],{"class":177},"  }\n",[167,820,822],{"class":169,"line":821},38,[167,823,341],{"class":177},[133,825,826],{},"A group says:",[828,829,830,843],"table",{},[831,832,833],"thead",{},[834,835,836,840],"tr",{},[837,838,839],"th",{},"Option",[837,841,842],{},"What it is",[844,845,846,857,871,881,891,905,918],"tbody",{},[834,847,848,854],{},[849,850,851],"td",{},[164,852,853],{},"id",[849,855,856],{},"The group's stable name, used to read and store it.",[834,858,859,868],{},[849,860,861,864,865],{},[164,862,863],{},"title",", ",[164,866,867],{},"description",[849,869,870],{},"How it reads on its form and in All settings.",[834,872,873,878],{},[849,874,875],{},[164,876,877],{},"fields",[849,879,880],{},"Its settings, by key. Keys are the names your code reads.",[834,882,883,888],{},[849,884,885],{},[164,886,887],{},"place",[849,889,890],{},"Where it is edited in your app, so All settings can link there.",[834,892,893,898],{},[849,894,895],{},[164,896,897],{},"entity",[849,899,900,901,150],{},"The pdd-kit Product Model Entity its fields are facts of. See ",[146,902,904],{"href":903},"#explanations","Explanations",[834,906,907,912],{},[849,908,909],{},[164,910,911],{},"rules",[849,913,914,915,150],{},"Checks between its own fields. See ",[146,916,28],{"href":917},".\u002Fchecks-and-guards",[834,919,920,925],{},[849,921,922],{},[164,923,924],{},"confirm",[849,926,927],{},"A question to answer before a costly change is saved.",[152,929,931],{"id":930},"what-a-field-says","What a field says",[133,933,934],{},"Every kind of field takes the same information about itself:",[828,936,937,945],{},[831,938,939],{},[834,940,941,943],{},[837,942,839],{},[837,944,842],{},[844,946,947,957,971,981,990,1000,1010],{},[834,948,949,954],{},[849,950,951],{},[164,952,953],{},"default",[849,955,956],{},"Its value until someone changes it. The one home of the default.",[834,958,959,964],{},[849,960,961],{},[164,962,963],{},"label",[849,965,966,967,970],{},"The name a person reads. Defaults to ",[164,968,969],{},"fact",", then to the key.",[834,972,973,978],{},[849,974,975],{},[164,976,977],{},"help",[849,979,980],{},"An explanation in the product's words, shown beside the label.",[834,982,983,987],{},[849,984,985],{},[164,986,969],{},[849,988,989],{},"The Product Model fact the field is. Its explanation comes from there.",[834,991,992,997],{},[849,993,994],{},[164,995,996],{},"section",[849,998,999],{},"The sub-heading it is listed under within its group.",[834,1001,1002,1007],{},[849,1003,1004],{},[164,1005,1006],{},"affects",[849,1008,1009],{},"What changing it changes, by effect id.",[834,1011,1012,1017],{},[849,1013,1014],{},[164,1015,1016],{},"override",[849,1018,1019,1020,150],{},"Where a workspace or a person may set its own value, and how far. See ",[146,1021,40],{"href":148},[133,1023,1024,1025,1028,1029,1032,1033,1036,1037,1040,1041,1044],{},"Each kind adds its own options, such as a ",[164,1026,1027],{},"number","'s ",[164,1030,1031],{},"unit"," and bounds; see\n",[146,1034,24],{"href":1035},".\u002Fkinds-and-units",". ",[164,1038,1039],{},"createTunables"," refuses a definition\nwhose default does not pass its own check, whose unit or effect is not\ndeclared, or whose ",[164,1042,1043],{},"pick"," names a catalog that does not exist. A mistake in a\ndefinition fails at start, in words, not on someone's first save.",[1046,1047,1049],"h3",{"id":1048},"effects","Effects",[133,1051,1052,1054,1055,1058,1059,1061,1062,1065,1066,1069],{},[164,1053,1006],{}," names what changing a field changes, by the ids you declare with\n",[164,1056,1057],{},"defineEffects",". Each effect has a ",[164,1060,963],{},", an optional Iconify ",[164,1063,1064],{},"icon",", and\n",[164,1067,1068],{},"warn"," for a change that costs money or reaches people. The screens show the\neffects under each field, and All settings can filter by them.",[1046,1071,1073],{"id":1072},"sections","Sections",[133,1075,1076,1078],{},[164,1077,996],{}," groups a form's fields under sub-headings, in the order their first\nfield appears. Fields without one are listed first, under General.",[152,1080,1082],{"id":1081},"reading-values","Reading values",[133,1084,1085,1087],{},[164,1086,1039],{}," takes your groups and returns typed reads:",[157,1089,1091],{"className":159,"code":1090,"language":161,"meta":162,"style":162},"const tunables = createTunables({ store, groups: [limits], effects })\n\nconst { maxProjects, titleLength } = await tunables.get('limits')\n\u002F\u002F maxProjects: number\n\u002F\u002F titleLength: { min: number, max: number }\n",[164,1092,1093,1130,1134,1178,1184],{"__ignoreMap":162},[167,1094,1095,1098,1101,1103,1106,1109,1112,1114,1117,1119,1121,1123,1126,1128],{"class":169,"line":170},[167,1096,1097],{"class":173},"const",[167,1099,1100],{"class":181}," tunables ",[167,1102,241],{"class":177},[167,1104,1105],{"class":244}," createTunables",[167,1107,1108],{"class":177},"({",[167,1110,1111],{"class":181}," store",[167,1113,185],{"class":177},[167,1115,1116],{"class":181}," groups",[167,1118,256],{"class":177},[167,1120,566],{"class":177},[167,1122,375],{"class":181},[167,1124,1125],{"class":177},"],",[167,1127,238],{"class":181},[167,1129,341],{"class":177},[167,1131,1132],{"class":169,"line":222},[167,1133,226],{"emptyLinePlaceholder":225},[167,1135,1136,1138,1140,1143,1145,1148,1150,1153,1156,1160,1163,1166,1169,1171,1173,1175],{"class":169,"line":229},[167,1137,1097],{"class":173},[167,1139,178],{"class":177},[167,1141,1142],{"class":181}," maxProjects",[167,1144,185],{"class":177},[167,1146,1147],{"class":181}," titleLength ",[167,1149,206],{"class":177},[167,1151,1152],{"class":177}," =",[167,1154,1155],{"class":173}," await",[167,1157,1159],{"class":1158},"sq0K3"," tunables",[167,1161,150],{"class":1162},"sBd0f",[167,1164,1165],{"class":244},"get",[167,1167,1168],{"class":177},"(",[167,1170,271],{"class":177},[167,1172,375],{"class":215},[167,1174,271],{"class":177},[167,1176,1177],{"class":177},")\n",[167,1179,1180],{"class":169,"line":250},[167,1181,1183],{"class":1182},"sKllz","\u002F\u002F maxProjects: number\n",[167,1185,1186],{"class":169,"line":291},[167,1187,1188],{"class":1182},"\u002F\u002F titleLength: { min: number, max: number }\n",[1190,1191,1192,1199,1212,1227,1234,1241],"ul",{},[1193,1194,1195,1198],"li",{},[137,1196,1197],{},"Read at the point of use, every time."," A read is one query. A value\ncaptured at boot needs a restart to change.",[1193,1200,1201,1204,1205,1207,1208,1211],{},[137,1202,1203],{},"Never write a fallback."," ",[164,1206,1165],{}," returns every field or throws: a\n",[164,1209,1210],{},"?? 3"," beside it would be a second home for the default.",[1193,1213,1214,1216,1217,1220,1221,150],{},[164,1215,1165],{}," throws ",[164,1218,1219],{},"TunablesMissing"," when the group, or one of its fields, has not\nbeen stored yet: run ",[146,1222,1224],{"href":1223},".\u002Fcli-values#sync",[164,1225,1226],{},"tunables sync",[1193,1228,1229,1230,1233],{},"It throws ",[164,1231,1232],{},"TunablesInvalid"," when a stored value no longer passes its check,\nfor example after its bounds were tightened.",[1193,1235,1236,1237,1240],{},"A long-running worker can pass ",[164,1238,1239],{},"{ lastGood: true }",": when a read fails, it\nreturns the last values this process read or saved, if it has any.",[1193,1242,1243,1244,1247,1248,1251,1252,150],{},"With ",[164,1245,1246],{},"scopes",", pass ",[164,1249,1250],{},"{ scope: { workspace: 'acme', user: 'dana' } }"," to\nread the values as that workspace and person get them, through their plan\nand their own overrides. See ",[146,1253,40],{"href":1254},".\u002Fscopes#reading-at-a-path",[133,1256,1257],{},"To react to changes made in this process, subscribe:",[157,1259,1261],{"className":159,"code":1260,"language":161,"meta":162,"style":162},"const stop = tunables.onChange('limits', (values) => {\n  suggestions.setEnabled(values.aiSuggestions)\n})\n",[164,1262,1263,1303,1324],{"__ignoreMap":162},[167,1264,1265,1267,1270,1272,1274,1276,1279,1281,1283,1285,1287,1289,1292,1295,1298,1301],{"class":169,"line":170},[167,1266,1097],{"class":173},[167,1268,1269],{"class":181}," stop ",[167,1271,241],{"class":177},[167,1273,1159],{"class":1158},[167,1275,150],{"class":1162},[167,1277,1278],{"class":244},"onChange",[167,1280,1168],{"class":177},[167,1282,271],{"class":177},[167,1284,375],{"class":215},[167,1286,271],{"class":177},[167,1288,185],{"class":177},[167,1290,1291],{"class":177}," (",[167,1293,1294],{"class":181},"values",[167,1296,1297],{"class":177},")",[167,1299,1300],{"class":173}," =>",[167,1302,462],{"class":177},[167,1304,1305,1308,1310,1313,1315,1317,1319,1322],{"class":169,"line":222},[167,1306,1307],{"class":1158},"  suggestions",[167,1309,150],{"class":1162},[167,1311,1312],{"class":244},"setEnabled",[167,1314,1168],{"class":177},[167,1316,1294],{"class":181},[167,1318,150],{"class":177},[167,1320,1321],{"class":181},"aiSuggestions",[167,1323,1177],{"class":177},[167,1325,1326],{"class":169,"line":229},[167,1327,341],{"class":177},[133,1329,1330],{},"The listener runs after a change to the group's system values commits. Other\nprocesses learn of it on their next read.",[1046,1332,1334],{"id":1333},"the-entry","The entry",[133,1336,1337,1340,1341,1343,1344,1347,1348,1351,1352,1355,1356,1359,1360,1364],{},[164,1338,1339],{},"tunables.entry(id)"," returns what a form needs: the stored ",[164,1342,1294],{},", the\n",[164,1345,1346],{},"defaults",", which keys are ",[164,1349,1350],{},"changed"," from their default, each value and default\nas it reads to a person (",[164,1353,1354],{},"display","), ",[164,1357,1358],{},"notes"," from your\n",[146,1361,1363],{"href":1362},".\u002Fchecks-and-guards#notes","notes provider",", and who changed the group last\nand when.",[157,1366,1368],{"className":159,"code":1367,"language":161,"meta":162,"style":162},"const entry = await tunables.entry('limits')\nentry.display.values.titleLength \u002F\u002F \"1–200 characters\"\nentry.changed \u002F\u002F [\"maxProjects\"] once it differs from its default\n",[164,1369,1370,1398,1418],{"__ignoreMap":162},[167,1371,1372,1374,1377,1379,1381,1383,1385,1388,1390,1392,1394,1396],{"class":169,"line":170},[167,1373,1097],{"class":173},[167,1375,1376],{"class":181}," entry ",[167,1378,241],{"class":177},[167,1380,1155],{"class":173},[167,1382,1159],{"class":1158},[167,1384,150],{"class":1162},[167,1386,1387],{"class":244},"entry",[167,1389,1168],{"class":177},[167,1391,271],{"class":177},[167,1393,375],{"class":215},[167,1395,271],{"class":177},[167,1397,1177],{"class":177},[167,1399,1400,1402,1404,1406,1408,1410,1412,1415],{"class":169,"line":222},[167,1401,1387],{"class":181},[167,1403,150],{"class":177},[167,1405,1354],{"class":181},[167,1407,150],{"class":177},[167,1409,1294],{"class":181},[167,1411,150],{"class":177},[167,1413,1414],{"class":181},"titleLength ",[167,1416,1417],{"class":1182},"\u002F\u002F \"1–200 characters\"\n",[167,1419,1420,1422,1424,1427],{"class":169,"line":229},[167,1421,1387],{"class":181},[167,1423,150],{"class":177},[167,1425,1426],{"class":181},"changed ",[167,1428,1429],{"class":1182},"\u002F\u002F [\"maxProjects\"] once it differs from its default\n",[152,1431,1433],{"id":1432},"saving-values","Saving values",[133,1435,1436],{},"A save takes the fields that change, merges them over the stored values, and\nchecks the whole group before anything is written:",[157,1438,1440],{"className":159,"code":1439,"language":161,"meta":162,"style":162},"const outcome = await tunables.save('limits', { maxProjects: 5 }, {\n  actor: { id: user.id, name: user.name },\n  reason: 'More room on the free tier'\n})\n\nif (!outcome.saved) {\n  for (const problem of outcome.problems) console.log(problem.key, problem.message)\n}\n",[164,1441,1442,1484,1522,1536,1540,1544,1564,1619],{"__ignoreMap":162},[167,1443,1444,1446,1449,1451,1453,1455,1457,1460,1462,1464,1466,1468,1470,1472,1474,1476,1479,1482],{"class":169,"line":170},[167,1445,1097],{"class":173},[167,1447,1448],{"class":181}," outcome ",[167,1450,241],{"class":177},[167,1452,1155],{"class":173},[167,1454,1159],{"class":1158},[167,1456,150],{"class":1162},[167,1458,1459],{"class":244},"save",[167,1461,1168],{"class":177},[167,1463,271],{"class":177},[167,1465,375],{"class":215},[167,1467,271],{"class":177},[167,1469,185],{"class":177},[167,1471,178],{"class":177},[167,1473,1142],{"class":181},[167,1475,256],{"class":177},[167,1477,1478],{"class":215}," 5",[167,1480,1481],{"class":177}," },",[167,1483,462],{"class":177},[167,1485,1486,1489,1491,1493,1496,1498,1501,1503,1505,1507,1510,1512,1514,1516,1519],{"class":169,"line":222},[167,1487,1488],{"class":181},"  actor",[167,1490,256],{"class":177},[167,1492,178],{"class":177},[167,1494,1495],{"class":181}," id",[167,1497,256],{"class":177},[167,1499,1500],{"class":181}," user",[167,1502,150],{"class":177},[167,1504,853],{"class":181},[167,1506,185],{"class":177},[167,1508,1509],{"class":181}," name",[167,1511,256],{"class":177},[167,1513,1500],{"class":181},[167,1515,150],{"class":177},[167,1517,1518],{"class":181},"name ",[167,1520,1521],{"class":177},"},\n",[167,1523,1524,1527,1529,1531,1534],{"class":169,"line":229},[167,1525,1526],{"class":181},"  reason",[167,1528,256],{"class":177},[167,1530,212],{"class":177},[167,1532,1533],{"class":215},"More room on the free tier",[167,1535,219],{"class":177},[167,1537,1538],{"class":169,"line":250},[167,1539,341],{"class":177},[167,1541,1542],{"class":169,"line":291},[167,1543,226],{"emptyLinePlaceholder":225},[167,1545,1546,1549,1552,1555,1557,1560,1562],{"class":169,"line":338},[167,1547,1548],{"class":173},"if",[167,1550,1551],{"class":177}," (!",[167,1553,1554],{"class":181},"outcome",[167,1556,150],{"class":177},[167,1558,1559],{"class":181},"saved",[167,1561,1297],{"class":177},[167,1563,462],{"class":177},[167,1565,1566,1569,1571,1573,1576,1579,1582,1584,1587,1589,1592,1594,1597,1599,1602,1604,1607,1609,1612,1614,1617],{"class":169,"line":344},[167,1567,1568],{"class":173},"  for",[167,1570,1291],{"class":177},[167,1572,1097],{"class":173},[167,1574,1575],{"class":181}," problem ",[167,1577,1578],{"class":177},"of",[167,1580,1581],{"class":181}," outcome",[167,1583,150],{"class":177},[167,1585,1586],{"class":181},"problems",[167,1588,1297],{"class":177},[167,1590,1591],{"class":1158}," console",[167,1593,150],{"class":1162},[167,1595,1596],{"class":244},"log",[167,1598,1168],{"class":177},[167,1600,1601],{"class":181},"problem",[167,1603,150],{"class":177},[167,1605,1606],{"class":181},"key",[167,1608,185],{"class":177},[167,1610,1611],{"class":181}," problem",[167,1613,150],{"class":177},[167,1615,1616],{"class":181},"message",[167,1618,1177],{"class":177},[167,1620,1621],{"class":169,"line":349},[167,1622,1623],{"class":177},"}\n",[133,1625,1626,1627,1629],{},"The outcome says what the change came to. Nothing is stored unless ",[164,1628,1559],{}," is\ntrue.",[828,1631,1632,1642],{},[831,1633,1634],{},[834,1635,1636,1639],{},[837,1637,1638],{},"Field",[837,1640,1641],{},"What it holds",[844,1643,1644,1653,1665,1677,1687],{},[834,1645,1646,1650],{},[849,1647,1648],{},[164,1649,1559],{},[849,1651,1652],{},"Whether the change was stored.",[834,1654,1655,1659],{},[849,1656,1657],{},[164,1658,1586],{},[849,1660,1661,1662,1664],{},"Each problem in words, with the ",[164,1663,1606],{}," of the field it is about when there is one.",[834,1666,1667,1671],{},[849,1668,1669],{},[164,1670,924],{},[849,1672,1673,1674,150],{},"Set when the change must be confirmed: send it again with ",[164,1675,1676],{},"confirmed: true",[834,1678,1679,1684],{},[849,1680,1681],{},[164,1682,1683],{},"revision",[849,1685,1686],{},"The revision recorded. Absent when nothing changed.",[834,1688,1689,1694],{},[849,1690,1691],{},[164,1692,1693],{},"value",[849,1695,1696],{},"The group's entry after the save.",[133,1698,1699],{},"The change options are the same everywhere:",[828,1701,1702,1710],{},[831,1703,1704],{},[834,1705,1706,1708],{},[837,1707,839],{},[837,1709,842],{},[844,1711,1712,1726,1736,1746,1756],{},[834,1713,1714,1719],{},[849,1715,1716],{},[164,1717,1718],{},"actor",[849,1720,1721,1722,1725],{},"Who made the change: a name, or ",[164,1723,1724],{},"{ id, name }",". Stored as the name.",[834,1727,1728,1733],{},[849,1729,1730],{},[164,1731,1732],{},"reason",[849,1734,1735],{},"Why, kept on the revision.",[834,1737,1738,1743],{},[849,1739,1740],{},[164,1741,1742],{},"confirmed",[849,1744,1745],{},"The change was sent again after it asked to be confirmed.",[834,1747,1748,1753],{},[849,1749,1750],{},[164,1751,1752],{},"host",[849,1754,1755],{},"Your open transaction or request context, handed to guards and notes.",[834,1757,1758,1763],{},[849,1759,1760],{},[164,1761,1762],{},"scope",[849,1764,1765,1766,1769,1770,150],{},"Where the change is made, such as ",[164,1767,1768],{},"{ workspace: 'acme' }",". Absent: the whole system. See ",[146,1771,40],{"href":1772},".\u002Fscopes#saving-at-a-scope",[133,1774,1775,1778],{},[164,1776,1777],{},"reset"," puts fields back at their defaults through the same checks:",[157,1780,1782],{"className":159,"code":1781,"language":161,"meta":162,"style":162},"await tunables.reset('limits', ['maxProjects'], { actor: 'Ada' })\nawait tunables.reset('limits') \u002F\u002F every field\n",[164,1783,1784,1833],{"__ignoreMap":162},[167,1785,1786,1789,1791,1793,1795,1797,1799,1801,1803,1805,1807,1809,1812,1814,1816,1818,1821,1823,1825,1828,1830],{"class":169,"line":170},[167,1787,1788],{"class":173},"await",[167,1790,1159],{"class":1158},[167,1792,150],{"class":1162},[167,1794,1777],{"class":244},[167,1796,1168],{"class":177},[167,1798,271],{"class":177},[167,1800,375],{"class":215},[167,1802,271],{"class":177},[167,1804,185],{"class":177},[167,1806,566],{"class":177},[167,1808,271],{"class":177},[167,1810,1811],{"class":215},"maxProjects",[167,1813,271],{"class":177},[167,1815,1125],{"class":177},[167,1817,178],{"class":177},[167,1819,1820],{"class":181}," actor",[167,1822,256],{"class":177},[167,1824,212],{"class":177},[167,1826,1827],{"class":215},"Ada",[167,1829,271],{"class":177},[167,1831,1832],{"class":177}," })\n",[167,1834,1835,1837,1839,1841,1843,1845,1847,1849,1851,1853],{"class":169,"line":222},[167,1836,1788],{"class":173},[167,1838,1159],{"class":1158},[167,1840,150],{"class":1162},[167,1842,1777],{"class":244},[167,1844,1168],{"class":177},[167,1846,271],{"class":177},[167,1848,375],{"class":215},[167,1850,271],{"class":177},[167,1852,1297],{"class":177},[167,1854,1855],{"class":1182}," \u002F\u002F every field\n",[152,1857,904],{"id":1858},"explanations",[133,1860,1861,1862,1864,1865,1867,1868,1870,1871,1877,1878,1880,1881,1883,1884,1890,1891,1893,1894,1036,1896,1899,1900,1902,1903,1905,1906,1908,1909,150],{},"A field explains itself beside its label: the question mark on the form, the\nhelp in All settings. When the field names a ",[164,1863,969],{}," and its group names an\n",[164,1866,897],{},", the explanation is the Product Model's own description of that fact;\notherwise it is the field's ",[164,1869,977],{},". With a ",[146,1872,1876],{"href":1873,"rel":1874},"https:\u002F\u002Fpdd-kit.dev",[1875],"nofollow","pdd-kit","\nProduct Model, write ",[164,1879,969],{}," instead of ",[164,1882,977],{},", run\n",[146,1885,1887],{"href":1886},".\u002Fcli-check#explain",[164,1888,1889],{},"tunables explain"," and pass the module it writes to\n",[164,1892,1039],{}," as ",[164,1895,1858],{},[164,1897,1898],{},"tunables check"," reports a field that\ncarries ",[164,1901,977],{}," for a fact the model describes, and a ",[164,1904,969],{}," on a group that\nnames no ",[164,1907,897],{},". See ",[146,1910,87],{"href":1886},[152,1912,1914],{"id":1913},"where-a-group-goes","Where a group goes",[133,1916,1917,1918,1920,1921,1923],{},"Put settings that bound one another in one group, because a group's ",[164,1919,911],{}," see\nonly its own values. Otherwise group by what the settings govern and who tunes\nthem, and give each group a ",[164,1922,887],{}," beside what it affects: limits on the\nadmin's Limits page, how a workspace works on the workspace's own settings\npage. All settings lists every group in one place, read-only, with a link to\neach.",[1925,1926,1927],"style",{},"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 .sjCzm, html code.shiki .sjCzm{--shiki-light:#92400E;--shiki-default:#92400E;--shiki-dark:#FCD34D}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 .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}html pre.shiki code .sKllz, html code.shiki .sKllz{--shiki-light:#4F5D63;--shiki-light-font-style:italic;--shiki-default:#4F5D63;--shiki-default-font-style:italic;--shiki-dark:#A3AFB4;--shiki-dark-font-style:italic}",{"title":162,"searchDepth":222,"depth":222,"links":1929},[1930,1931,1935,1938,1939,1940],{"id":154,"depth":222,"text":155},{"id":930,"depth":222,"text":931,"children":1932},[1933,1934],{"id":1048,"depth":229,"text":1049},{"id":1072,"depth":229,"text":1073},{"id":1081,"depth":222,"text":1082,"children":1936},[1937],{"id":1333,"depth":229,"text":1334},{"id":1432,"depth":222,"text":1433},{"id":1858,"depth":222,"text":904},{"id":1913,"depth":222,"text":1914},"md",{},{"title":19,"description":20},"docs\u002Fsettings-and-groups","PQFE0VeYG4w1Znp7amu08iRLxhCPCCVS5DlIYvFmZWU",1792845117973]