[{"data":1,"prerenderedAt":1694},["ShallowReactive",2],{"docs-sections":3,"docs-page-\u002Fdocs\u002Fkinds-and-units":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":24,"body":125,"description":25,"extension":1689,"group":21,"meta":1690,"navigation":423,"order":497,"path":23,"seo":1691,"stem":1692,"__hash__":1693},"docs\u002Fdocs\u002Fkinds-and-units.md",{"type":126,"value":127,"toc":1681},"minimark",[128,132,136,141,340,1051,1071,1075,1078,1161,1167,1171,1180,1348,1369,1374,1380,1613,1637,1641,1677],[129,130,24],"h1",{"id":131},"kinds-and-units",[133,134,135],"p",{},"Every field is declared with a builder that names its kind. The kind decides\nthe value's type, its check, the input the screens draw for it, and how it\nreads to a person.",[137,138,140],"h2",{"id":139},"the-kinds","The kinds",[142,143,144,168],"table",{},[145,146,147],"thead",{},[148,149,150,154,157,160],"tr",{},[151,152,153],"th",{},"Builder",[151,155,156],{},"Value",[151,158,159],{},"Input",[151,161,162,163],{},"Options beyond the ",[164,165,167],"a",{"href":166},".\u002Fsettings-and-groups#what-a-field-says","common ones",[169,170,171,202,223,241,258,276,296,322],"tbody",{},[148,172,173,180,184,187],{},[174,175,176],"td",{},[177,178,179],"code",{},"number",[174,181,182],{},[177,183,179],{},[174,185,186],{},"A number with its unit",[174,188,189,192,193,192,196,192,199],{},[177,190,191],{},"unit",", ",[177,194,195],{},"integer",[177,197,198],{},"min",[177,200,201],{},"max",[148,203,204,209,214,217],{},[174,205,206],{},[177,207,208],{},"range",[174,210,211],{},[177,212,213],{},"{ min, max }",[174,215,216],{},"A shortest and a longest",[174,218,219,192,221],{},[177,220,191],{},[177,222,195],{},[148,224,225,230,235,238],{},[174,226,227],{},[177,228,229],{},"size",[174,231,232],{},[177,233,234],{},"{ width, height }",[174,236,237],{},"Width and height in pixels",[174,239,240],{},"none",[148,242,243,248,253,256],{},[174,244,245],{},[177,246,247],{},"toggle",[174,249,250],{},[177,251,252],{},"boolean",[174,254,255],{},"A switch",[174,257,240],{},[148,259,260,265,268,271],{},[174,261,262],{},[177,263,264],{},"choice",[174,266,267],{},"one of the options",[174,269,270],{},"Radio buttons, or a select past three options",[174,272,273],{},[177,274,275],{},"options",[148,277,278,283,286,289],{},[174,279,280],{},[177,281,282],{},"choices",[174,284,285],{},"several of the options",[174,287,288],{},"Checkboxes",[174,290,291,192,293],{},[177,292,275],{},[177,294,295],{},"minimum",[148,297,298,303,308,311],{},[174,299,300],{},[177,301,302],{},"text",[174,304,305],{},[177,306,307],{},"string",[174,309,310],{},"A text box, or several lines",[174,312,313,192,316,192,319],{},[177,314,315],{},"maxLength",[177,317,318],{},"optional",[177,320,321],{},"multiline",[148,323,324,329,332,335],{},[174,325,326],{},[177,327,328],{},"pick",[174,330,331],{},"an entry id",[174,333,334],{},"A select of a catalog's Available entries",[174,336,337],{},[177,338,339],{},"catalog",[341,342,347],"pre",{"className":343,"code":344,"language":345,"meta":346,"style":346},"language-ts shiki shiki-themes tunables-panel-light tunables-panel-light tunables-panel-dark","import { choice, choices, number, pick, range, size, text, toggle } from 'tunables'\n\nconst fields = {\n  maxProjects: number({ label: 'Projects', unit: 'count', default: 3 }),\n  attachmentMb: number({ label: 'Attachment size', unit: 'megabytes', integer: false, default: 10 }),\n  titleLength: range({ label: 'Task title length', unit: 'characters', default: { min: 1, max: 200 } }),\n  coverImage: size({ label: 'Project cover size', default: { width: 1200, height: 400 } }),\n  guestAccess: toggle({ label: 'Guests', default: false }),\n  defaultView: choice({\n    label: 'Default view',\n    options: [{ value: 'list', label: 'List' }, { value: 'board', label: 'Board' }, { value: 'calendar', label: 'Calendar' }],\n    default: 'list'\n  }),\n  notifyBy: choices({ label: 'Notify by', options: ['email', 'push', 'digest'], default: ['email'], minimum: 0 }),\n  welcome: text({ label: 'Welcome message', default: '', maxLength: 2000, multiline: true }),\n  suggestionModel: pick({ label: 'Suggestion model', catalog: 'models', default: 'small' })\n}\n","ts","",[177,348,349,418,425,440,495,552,619,672,706,719,737,833,847,853,938,992,1045],{"__ignoreMap":346},[350,351,354,358,362,366,369,372,374,377,379,382,384,387,389,392,394,397,399,402,405,408,411,415],"span",{"class":352,"line":353},"line",1,[350,355,357],{"class":356},"sZ4mS","import",[350,359,361],{"class":360},"sp9ci"," {",[350,363,365],{"class":364},"sVz71"," choice",[350,367,368],{"class":360},",",[350,370,371],{"class":364}," choices",[350,373,368],{"class":360},[350,375,376],{"class":364}," number",[350,378,368],{"class":360},[350,380,381],{"class":364}," pick",[350,383,368],{"class":360},[350,385,386],{"class":364}," range",[350,388,368],{"class":360},[350,390,391],{"class":364}," size",[350,393,368],{"class":360},[350,395,396],{"class":364}," text",[350,398,368],{"class":360},[350,400,401],{"class":364}," toggle ",[350,403,404],{"class":360},"}",[350,406,407],{"class":356}," from",[350,409,410],{"class":360}," '",[350,412,414],{"class":413},"sjCzm","tunables",[350,416,417],{"class":360},"'\n",[350,419,421],{"class":352,"line":420},2,[350,422,424],{"emptyLinePlaceholder":423},true,"\n",[350,426,428,431,434,437],{"class":352,"line":427},3,[350,429,430],{"class":356},"const",[350,432,433],{"class":364}," fields ",[350,435,436],{"class":360},"=",[350,438,439],{"class":360}," {\n",[350,441,443,446,449,452,455,458,460,462,465,468,470,473,475,477,480,482,484,487,489,492],{"class":352,"line":442},4,[350,444,445],{"class":364},"  maxProjects",[350,447,448],{"class":360},":",[350,450,376],{"class":451},"sOThN",[350,453,454],{"class":360},"({",[350,456,457],{"class":364}," label",[350,459,448],{"class":360},[350,461,410],{"class":360},[350,463,464],{"class":413},"Projects",[350,466,467],{"class":360},"'",[350,469,368],{"class":360},[350,471,472],{"class":364}," unit",[350,474,448],{"class":360},[350,476,410],{"class":360},[350,478,479],{"class":413},"count",[350,481,467],{"class":360},[350,483,368],{"class":360},[350,485,486],{"class":364}," default",[350,488,448],{"class":360},[350,490,491],{"class":413}," 3",[350,493,494],{"class":360}," }),\n",[350,496,498,501,503,505,507,509,511,513,516,518,520,522,524,526,529,531,533,536,538,541,543,545,547,550],{"class":352,"line":497},5,[350,499,500],{"class":364},"  attachmentMb",[350,502,448],{"class":360},[350,504,376],{"class":451},[350,506,454],{"class":360},[350,508,457],{"class":364},[350,510,448],{"class":360},[350,512,410],{"class":360},[350,514,515],{"class":413},"Attachment size",[350,517,467],{"class":360},[350,519,368],{"class":360},[350,521,472],{"class":364},[350,523,448],{"class":360},[350,525,410],{"class":360},[350,527,528],{"class":413},"megabytes",[350,530,467],{"class":360},[350,532,368],{"class":360},[350,534,535],{"class":364}," integer",[350,537,448],{"class":360},[350,539,540],{"class":356}," false",[350,542,368],{"class":360},[350,544,486],{"class":364},[350,546,448],{"class":360},[350,548,549],{"class":413}," 10",[350,551,494],{"class":360},[350,553,555,558,560,562,564,566,568,570,573,575,577,579,581,583,586,588,590,592,594,596,599,601,604,606,609,611,614,617],{"class":352,"line":554},6,[350,556,557],{"class":364},"  titleLength",[350,559,448],{"class":360},[350,561,386],{"class":451},[350,563,454],{"class":360},[350,565,457],{"class":364},[350,567,448],{"class":360},[350,569,410],{"class":360},[350,571,572],{"class":413},"Task title length",[350,574,467],{"class":360},[350,576,368],{"class":360},[350,578,472],{"class":364},[350,580,448],{"class":360},[350,582,410],{"class":360},[350,584,585],{"class":413},"characters",[350,587,467],{"class":360},[350,589,368],{"class":360},[350,591,486],{"class":364},[350,593,448],{"class":360},[350,595,361],{"class":360},[350,597,598],{"class":364}," min",[350,600,448],{"class":360},[350,602,603],{"class":413}," 1",[350,605,368],{"class":360},[350,607,608],{"class":364}," max",[350,610,448],{"class":360},[350,612,613],{"class":413}," 200",[350,615,616],{"class":360}," }",[350,618,494],{"class":360},[350,620,622,625,627,629,631,633,635,637,640,642,644,646,648,650,653,655,658,660,663,665,668,670],{"class":352,"line":621},7,[350,623,624],{"class":364},"  coverImage",[350,626,448],{"class":360},[350,628,391],{"class":451},[350,630,454],{"class":360},[350,632,457],{"class":364},[350,634,448],{"class":360},[350,636,410],{"class":360},[350,638,639],{"class":413},"Project cover size",[350,641,467],{"class":360},[350,643,368],{"class":360},[350,645,486],{"class":364},[350,647,448],{"class":360},[350,649,361],{"class":360},[350,651,652],{"class":364}," width",[350,654,448],{"class":360},[350,656,657],{"class":413}," 1200",[350,659,368],{"class":360},[350,661,662],{"class":364}," height",[350,664,448],{"class":360},[350,666,667],{"class":413}," 400",[350,669,616],{"class":360},[350,671,494],{"class":360},[350,673,675,678,680,683,685,687,689,691,694,696,698,700,702,704],{"class":352,"line":674},8,[350,676,677],{"class":364},"  guestAccess",[350,679,448],{"class":360},[350,681,682],{"class":451}," toggle",[350,684,454],{"class":360},[350,686,457],{"class":364},[350,688,448],{"class":360},[350,690,410],{"class":360},[350,692,693],{"class":413},"Guests",[350,695,467],{"class":360},[350,697,368],{"class":360},[350,699,486],{"class":364},[350,701,448],{"class":360},[350,703,540],{"class":356},[350,705,494],{"class":360},[350,707,709,712,714,716],{"class":352,"line":708},9,[350,710,711],{"class":364},"  defaultView",[350,713,448],{"class":360},[350,715,365],{"class":451},[350,717,718],{"class":360},"({\n",[350,720,722,725,727,729,732,734],{"class":352,"line":721},10,[350,723,724],{"class":364},"    label",[350,726,448],{"class":360},[350,728,410],{"class":360},[350,730,731],{"class":413},"Default view",[350,733,467],{"class":360},[350,735,736],{"class":360},",\n",[350,738,740,743,745,748,751,753,755,758,760,762,764,766,768,771,773,776,778,780,782,784,787,789,791,793,795,797,800,802,804,806,808,810,812,815,817,819,821,823,825,828,830],{"class":352,"line":739},11,[350,741,742],{"class":364},"    options",[350,744,448],{"class":360},[350,746,747],{"class":360}," [{",[350,749,750],{"class":364}," value",[350,752,448],{"class":360},[350,754,410],{"class":360},[350,756,757],{"class":413},"list",[350,759,467],{"class":360},[350,761,368],{"class":360},[350,763,457],{"class":364},[350,765,448],{"class":360},[350,767,410],{"class":360},[350,769,770],{"class":413},"List",[350,772,467],{"class":360},[350,774,775],{"class":360}," },",[350,777,361],{"class":360},[350,779,750],{"class":364},[350,781,448],{"class":360},[350,783,410],{"class":360},[350,785,786],{"class":413},"board",[350,788,467],{"class":360},[350,790,368],{"class":360},[350,792,457],{"class":364},[350,794,448],{"class":360},[350,796,410],{"class":360},[350,798,799],{"class":413},"Board",[350,801,467],{"class":360},[350,803,775],{"class":360},[350,805,361],{"class":360},[350,807,750],{"class":364},[350,809,448],{"class":360},[350,811,410],{"class":360},[350,813,814],{"class":413},"calendar",[350,816,467],{"class":360},[350,818,368],{"class":360},[350,820,457],{"class":364},[350,822,448],{"class":360},[350,824,410],{"class":360},[350,826,827],{"class":413},"Calendar",[350,829,467],{"class":360},[350,831,832],{"class":360}," }],\n",[350,834,836,839,841,843,845],{"class":352,"line":835},12,[350,837,838],{"class":364},"    default",[350,840,448],{"class":360},[350,842,410],{"class":360},[350,844,757],{"class":413},[350,846,417],{"class":360},[350,848,850],{"class":352,"line":849},13,[350,851,852],{"class":360},"  }),\n",[350,854,856,859,861,863,865,867,869,871,874,876,878,881,883,886,888,891,893,895,897,900,902,904,906,909,911,914,916,918,920,922,924,926,928,931,933,936],{"class":352,"line":855},14,[350,857,858],{"class":364},"  notifyBy",[350,860,448],{"class":360},[350,862,371],{"class":451},[350,864,454],{"class":360},[350,866,457],{"class":364},[350,868,448],{"class":360},[350,870,410],{"class":360},[350,872,873],{"class":413},"Notify by",[350,875,467],{"class":360},[350,877,368],{"class":360},[350,879,880],{"class":364}," options",[350,882,448],{"class":360},[350,884,885],{"class":360}," [",[350,887,467],{"class":360},[350,889,890],{"class":413},"email",[350,892,467],{"class":360},[350,894,368],{"class":360},[350,896,410],{"class":360},[350,898,899],{"class":413},"push",[350,901,467],{"class":360},[350,903,368],{"class":360},[350,905,410],{"class":360},[350,907,908],{"class":413},"digest",[350,910,467],{"class":360},[350,912,913],{"class":360},"],",[350,915,486],{"class":364},[350,917,448],{"class":360},[350,919,885],{"class":360},[350,921,467],{"class":360},[350,923,890],{"class":413},[350,925,467],{"class":360},[350,927,913],{"class":360},[350,929,930],{"class":364}," minimum",[350,932,448],{"class":360},[350,934,935],{"class":413}," 0",[350,937,494],{"class":360},[350,939,941,944,946,948,950,952,954,956,959,961,963,965,967,970,972,975,977,980,982,985,987,990],{"class":352,"line":940},15,[350,942,943],{"class":364},"  welcome",[350,945,448],{"class":360},[350,947,396],{"class":451},[350,949,454],{"class":360},[350,951,457],{"class":364},[350,953,448],{"class":360},[350,955,410],{"class":360},[350,957,958],{"class":413},"Welcome message",[350,960,467],{"class":360},[350,962,368],{"class":360},[350,964,486],{"class":364},[350,966,448],{"class":360},[350,968,969],{"class":360}," ''",[350,971,368],{"class":360},[350,973,974],{"class":364}," maxLength",[350,976,448],{"class":360},[350,978,979],{"class":413}," 2000",[350,981,368],{"class":360},[350,983,984],{"class":364}," multiline",[350,986,448],{"class":360},[350,988,989],{"class":356}," true",[350,991,494],{"class":360},[350,993,995,998,1000,1002,1004,1006,1008,1010,1013,1015,1017,1020,1022,1024,1027,1029,1031,1033,1035,1037,1040,1042],{"class":352,"line":994},16,[350,996,997],{"class":364},"  suggestionModel",[350,999,448],{"class":360},[350,1001,381],{"class":451},[350,1003,454],{"class":360},[350,1005,457],{"class":364},[350,1007,448],{"class":360},[350,1009,410],{"class":360},[350,1011,1012],{"class":413},"Suggestion model",[350,1014,467],{"class":360},[350,1016,368],{"class":360},[350,1018,1019],{"class":364}," catalog",[350,1021,448],{"class":360},[350,1023,410],{"class":360},[350,1025,1026],{"class":413},"models",[350,1028,467],{"class":360},[350,1030,368],{"class":360},[350,1032,486],{"class":364},[350,1034,448],{"class":360},[350,1036,410],{"class":360},[350,1038,1039],{"class":413},"small",[350,1041,467],{"class":360},[350,1043,1044],{"class":360}," })\n",[350,1046,1048],{"class":352,"line":1047},17,[350,1049,1050],{"class":360},"}\n",[133,1052,1053,1055,1056,1058,1059,1062,1063,1066,1067,1070],{},[177,1054,264],{}," and ",[177,1057,282],{}," take options as plain strings, or as ",[177,1060,1061],{},"{ value, label }","\nwhen the stored value and the name a person reads differ. Their types are\nnarrowed to the options, so ",[177,1064,1065],{},"defaultView"," reads as\n",[177,1068,1069],{},"'list' | 'board' | 'calendar'",".",[137,1072,1074],{"id":1073},"how-values-are-checked","How values are checked",[133,1076,1077],{},"Each kind's check follows from its declaration, and every problem is said in\nthe field's own words:",[1079,1080,1081,1109,1116,1123,1138,1151],"ul",{},[1082,1083,1084,1089,1090,1093,1094,1096,1097,1055,1099,1101,1102,192,1106],"li",{},[1085,1086,1087],"strong",{},[177,1088,179],{}," is a whole number unless ",[177,1091,1092],{},"integer: false",", more than 0 unless a\n",[177,1095,198],{}," says otherwise, and within ",[177,1098,198],{},[177,1100,201],{},", which are inclusive. A\nunit's own bounds apply when the field states none. ",[1103,1104,1105],"em",{},"\"Projects must be a\nwhole number.\"",[1103,1107,1108],{},"\"Attachment size may not be more than 2,048 MB.\"",[1082,1110,1111,1115],{},[1085,1112,1113],{},[177,1114,208],{}," has a shortest and a longest, each checked like a number, and the\nshortest may not be longer than the longest.",[1082,1117,1118,1122],{},[1085,1119,1120],{},[177,1121,229],{}," has a whole-number width and height, each more than 0.",[1082,1124,1125,1129,1130,1134,1135,1137],{},[1085,1126,1127],{},[177,1128,264],{}," must be one of its options; ",[1085,1131,1132],{},[177,1133,282],{}," may hold only its\noptions, each once, and at least ",[177,1136,295],{}," of them (1 unless you say 0).",[1082,1139,1140,1144,1145,1147,1148,1070],{},[1085,1141,1142],{},[177,1143,302],{}," may be at most ",[177,1146,315],{}," characters, and may not be empty when\n",[177,1149,1150],{},"optional: false",[1082,1152,1153,1157,1158,1070],{},[1085,1154,1155],{},[177,1156,328],{}," must name an entry of its catalog. A setting or a preset may not\nnewly pick a Retired entry; see ",[164,1159,44],{"href":1160},".\u002Fcatalogs",[133,1162,1163,1166],{},[177,1164,1165],{},"fieldSchema(key, field, unitOf)"," returns the zod schema a field is checked\nwith, for code that needs the same check outside a save.",[137,1168,1170],{"id":1169},"units","Units",[133,1172,1173,1174,1176,1177,1179],{},"A ",[177,1175,179],{}," or ",[177,1178,208],{}," is measured in a unit. A unit says how an amount reads to\na person and what values it may take.",[142,1181,1182,1195],{},[145,1183,1184],{},[148,1185,1186,1189,1192],{},[151,1187,1188],{},"Unit",[151,1190,1191],{},"Reads as",[151,1193,1194],{},"Allows",[169,1196,1197,1211,1234,1253,1272,1286,1301,1318,1333],{},[148,1198,1199,1203,1208],{},[174,1200,1201],{},[177,1202,479],{},[174,1204,1205],{},[177,1206,1207],{},"12",[174,1209,1210],{},"any positive amount",[148,1212,1213,1227,1232],{},[174,1214,1215,192,1218,192,1221,192,1224],{},[177,1216,1217],{},"seconds",[177,1219,1220],{},"minutes",[177,1222,1223],{},"hours",[177,1225,1226],{},"days",[174,1228,1229],{},[177,1230,1231],{},"15 seconds",[174,1233,1210],{},[148,1235,1236,1246,1251],{},[174,1237,1238,192,1240,192,1243],{},[177,1239,585],{},[177,1241,1242],{},"words",[177,1244,1245],{},"tokens",[174,1247,1248],{},[177,1249,1250],{},"280 characters",[174,1252,1210],{},[148,1254,1255,1262,1270],{},[174,1256,1257,192,1260],{},[177,1258,1259],{},"kilobytes",[177,1261,528],{},[174,1263,1264,192,1267],{},[177,1265,1266],{},"100 KB",[177,1268,1269],{},"2 MB",[174,1271,1210],{},[148,1273,1274,1279,1284],{},[174,1275,1276],{},[177,1277,1278],{},"pixels",[174,1280,1281],{},[177,1282,1283],{},"400 pixels",[174,1285,1210],{},[148,1287,1288,1293,1298],{},[174,1289,1290],{},[177,1291,1292],{},"percent",[174,1294,1295],{},[177,1296,1297],{},"40%",[174,1299,1300],{},"0 to 100",[148,1302,1303,1308,1316],{},[174,1304,1305],{},[177,1306,1307],{},"dollars",[174,1309,1310,192,1313],{},[177,1311,1312],{},"$25",[177,1314,1315],{},"$0.10",[174,1317,1210],{},[148,1319,1320,1325,1330],{},[174,1321,1322],{},[177,1323,1324],{},"hour",[174,1326,1327],{},[177,1328,1329],{},"06:00",[174,1331,1332],{},"a whole hour of the day, 0 to 23",[148,1334,1335,1340,1345],{},[174,1336,1337],{},[177,1338,1339],{},"weekday",[174,1341,1342],{},[177,1343,1344],{},"Monday",[174,1346,1347],{},"0 (Sunday) to 6 (Saturday)",[133,1349,1350,192,1352,1055,1354,1356,1357,1359,1360,1055,1362,1364,1365,1368],{},[177,1351,1324],{},[177,1353,1339],{},[177,1355,1292],{}," allow 0 because their own minimum is 0; other\nunits start above 0. Whatever the unit, a number is whole unless the field\nsays ",[177,1358,1092],{},", so a dollar amount with cents needs it. The screens\ndraw ",[177,1361,1324],{},[177,1363,1339],{}," as selects and put ",[177,1366,1367],{},"$"," before dollars.",[1370,1371,1373],"h3",{"id":1372},"your-own-units","Your own units",[133,1375,1376,1377,448],{},"Declare a unit your app measures in and pass it to ",[177,1378,1379],{},"createTunables",[341,1381,1383],{"className":343,"code":1382,"language":345,"meta":346,"style":346},"import { createTunables, number, type Unit } from 'tunables'\n\nexport const units = {\n  cents: {\n    label: 'cents',\n    format: (value: number) => `${value}¢`\n  }\n} satisfies Record\u003Cstring, Unit>\n\nconst centsPerSuggestion = number({ label: 'Cost a suggestion', unit: 'cents', integer: false, default: 1 })\n\nconst tunables = createTunables({ store, groups, units })\n",[177,1384,1385,1416,1420,1435,1444,1459,1495,1500,1523,1527,1581,1585],{"__ignoreMap":346},[350,1386,1387,1389,1391,1394,1396,1398,1400,1403,1406,1408,1410,1412,1414],{"class":352,"line":353},[350,1388,357],{"class":356},[350,1390,361],{"class":360},[350,1392,1393],{"class":364}," createTunables",[350,1395,368],{"class":360},[350,1397,376],{"class":364},[350,1399,368],{"class":360},[350,1401,1402],{"class":356}," type",[350,1404,1405],{"class":364}," Unit ",[350,1407,404],{"class":360},[350,1409,407],{"class":356},[350,1411,410],{"class":360},[350,1413,414],{"class":413},[350,1415,417],{"class":360},[350,1417,1418],{"class":352,"line":420},[350,1419,424],{"emptyLinePlaceholder":423},[350,1421,1422,1425,1428,1431,1433],{"class":352,"line":427},[350,1423,1424],{"class":356},"export",[350,1426,1427],{"class":356}," const",[350,1429,1430],{"class":364}," units ",[350,1432,436],{"class":360},[350,1434,439],{"class":360},[350,1436,1437,1440,1442],{"class":352,"line":442},[350,1438,1439],{"class":364},"  cents",[350,1441,448],{"class":360},[350,1443,439],{"class":360},[350,1445,1446,1448,1450,1452,1455,1457],{"class":352,"line":497},[350,1447,724],{"class":364},[350,1449,448],{"class":360},[350,1451,410],{"class":360},[350,1453,1454],{"class":413},"cents",[350,1456,467],{"class":360},[350,1458,736],{"class":360},[350,1460,1461,1464,1466,1469,1472,1474,1476,1479,1482,1485,1487,1489,1492],{"class":352,"line":554},[350,1462,1463],{"class":451},"    format",[350,1465,448],{"class":360},[350,1467,1468],{"class":360}," (",[350,1470,1471],{"class":364},"value",[350,1473,448],{"class":360},[350,1475,376],{"class":451},[350,1477,1478],{"class":360},")",[350,1480,1481],{"class":356}," =>",[350,1483,1484],{"class":360}," `${",[350,1486,1471],{"class":364},[350,1488,404],{"class":360},[350,1490,1491],{"class":413},"¢",[350,1493,1494],{"class":360},"`\n",[350,1496,1497],{"class":352,"line":621},[350,1498,1499],{"class":360},"  }\n",[350,1501,1502,1504,1507,1510,1513,1515,1517,1520],{"class":352,"line":674},[350,1503,404],{"class":360},[350,1505,1506],{"class":356}," satisfies",[350,1508,1509],{"class":451}," Record",[350,1511,1512],{"class":360},"\u003C",[350,1514,307],{"class":451},[350,1516,368],{"class":360},[350,1518,1519],{"class":451}," Unit",[350,1521,1522],{"class":360},">\n",[350,1524,1525],{"class":352,"line":708},[350,1526,424],{"emptyLinePlaceholder":423},[350,1528,1529,1531,1534,1536,1538,1540,1542,1544,1546,1549,1551,1553,1555,1557,1559,1561,1563,1565,1567,1569,1571,1573,1575,1577,1579],{"class":352,"line":721},[350,1530,430],{"class":356},[350,1532,1533],{"class":364}," centsPerSuggestion ",[350,1535,436],{"class":360},[350,1537,376],{"class":451},[350,1539,454],{"class":360},[350,1541,457],{"class":364},[350,1543,448],{"class":360},[350,1545,410],{"class":360},[350,1547,1548],{"class":413},"Cost a suggestion",[350,1550,467],{"class":360},[350,1552,368],{"class":360},[350,1554,472],{"class":364},[350,1556,448],{"class":360},[350,1558,410],{"class":360},[350,1560,1454],{"class":413},[350,1562,467],{"class":360},[350,1564,368],{"class":360},[350,1566,535],{"class":364},[350,1568,448],{"class":360},[350,1570,540],{"class":356},[350,1572,368],{"class":360},[350,1574,486],{"class":364},[350,1576,448],{"class":360},[350,1578,603],{"class":413},[350,1580,1044],{"class":360},[350,1582,1583],{"class":352,"line":739},[350,1584,424],{"emptyLinePlaceholder":423},[350,1586,1587,1589,1592,1594,1596,1598,1601,1603,1606,1608,1610],{"class":352,"line":835},[350,1588,430],{"class":356},[350,1590,1591],{"class":364}," tunables ",[350,1593,436],{"class":360},[350,1595,1393],{"class":451},[350,1597,454],{"class":360},[350,1599,1600],{"class":364}," store",[350,1602,368],{"class":360},[350,1604,1605],{"class":364}," groups",[350,1607,368],{"class":360},[350,1609,1430],{"class":364},[350,1611,1612],{"class":360},"})\n",[133,1614,1615,1616,1619,1620,192,1622,192,1624,1626,1627,1630,1631],{},"A unit takes a ",[177,1617,1618],{},"label",", and optionally ",[177,1621,198],{},[177,1623,201],{},[177,1625,195],{}," and a ",[177,1628,1629],{},"format","\nfunction. A field measured in a unit nobody declared fails at start: ",[1103,1632,1633,1634,1636],{},"\"models.centsPerSuggestion\nis measured in \"cents\", which is not a unit. Add it to ",[177,1635,1169],{},".\"",[137,1638,1640],{"id":1639},"how-values-read","How values read",[133,1642,1643,1646,1647,1650,1651,1654,1655,192,1657,192,1660,192,1663,192,1666,1668,1669,1672,1673,1676],{},[177,1644,1645],{},"formatValue(field, value, unitOf)"," gives the reading the screens, All\nsettings, ",[177,1648,1649],{},"tunables get"," and the entry's ",[177,1652,1653],{},"display"," use: ",[177,1656,1312],{},[177,1658,1659],{},"1–200 characters",[177,1661,1662],{},"1200 × 400 pixels",[177,1664,1665],{},"On",[177,1667,827],{},". In the browser,\n",[177,1670,1671],{},"displayValue"," from ",[177,1674,1675],{},"tunables\u002Fclient"," reads a value from a field's description\nthe same way, for a value still being edited.",[1678,1679,1680],"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);}",{"title":346,"searchDepth":420,"depth":420,"links":1682},[1683,1684,1685,1688],{"id":139,"depth":420,"text":140},{"id":1073,"depth":420,"text":1074},{"id":1169,"depth":420,"text":1170,"children":1686},[1687],{"id":1372,"depth":427,"text":1373},{"id":1639,"depth":420,"text":1640},"md",{},{"title":24,"description":25},"docs\u002Fkinds-and-units","pjWMZK1tdznr4FAS25QOOewn-U6hK8dooGpkctfczcA",1792845119345]