[{"data":1,"prerenderedAt":580},["ShallowReactive",2],{"docs-sections":3,"docs-page-\u002Fdocs":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":6,"body":125,"description":7,"extension":573,"group":8,"meta":574,"navigation":575,"order":576,"path":5,"seo":577,"stem":578,"__hash__":579},"docs\u002Fdocs\u002Findex.md",{"type":126,"value":127,"toc":564},"minimark",[128,132,141,144,149,152,180,188,192,195,254,258,266,378,382,536,552,556],[129,130,6],"h1",{"id":131},"introduction",[133,134,135,136,140],"p",{},"tunables is a TypeScript library for ",[137,138,139],"strong",{},"runtime settings",": the limits,\nintervals, budgets and switches someone needs to change while your app runs.\nYou declare each setting once. tunables keeps its value in your own Postgres,\nputs it on a screen inside your own app, checks every change as a whole, and\nrecords each change as a revision you can read and restore.",[133,142,143],{},"It is not a service. There is nothing to deploy beside your app and no account\nto create: tunables runs in your process, through your database client, behind\nyour sign-in.",[145,146,148],"h2",{"id":147},"the-problem","The problem",[133,150,151],{},"A value someone needs to tune usually ends up in one of four places:",[153,154,155,162,168,174],"ul",{},[156,157,158,161],"li",{},[137,159,160],{},"A constant in code."," Changing it is a deploy, and nobody outside\nengineering can see it.",[156,163,164,167],{},[137,165,166],{},"An environment variable."," Read at boot, set per deployment, and invisible\nto the people who decide it.",[156,169,170,173],{},[137,171,172],{},"A one-off admin form."," It checks its own fields, if it checks anything,\nand nothing else.",[156,175,176,179],{},[137,177,178],{},"A key–value table."," A name and a blob, with no unit, no default and no\nexplanation.",[133,181,182,183,187],{},"None of them keeps a history, so nobody can say who changed a value, what it\nwas before or why. None of them knows what the value bounds, so lowering a\nlimit quietly strands whatever was configured above it. And when the same value\ncan come from the environment ",[184,185,186],"em",{},"and"," the database, the app grows a precedence\nrule that nobody remembers.",[145,189,191],{"id":190},"what-tunables-does-instead","What tunables does instead",[133,193,194],{},"Every tunable is a knob on a panel:",[196,197,198,209,227,236,245],"ol",{},[156,199,200,203,204,208],{},[137,201,202],{},"Declare it once."," A setting states its kind, unit, default, explanation,\nsection and what changing it affects. Its check, its stored seed, the API's\nschema and its form input all follow from that one declaration. See\n",[205,206,19],"a",{"href":207},".\u002Fdocs\u002Fsettings-and-groups",".",[156,210,211,214,215,219,220,223,224,208],{},[137,212,213],{},"Store it in your Postgres."," Four tables in a schema of your database,\nwritten through the ",[216,217,218],"code",{},"pg"," or ",[216,221,222],{},"postgres"," client you already have. See\n",[205,225,52],{"href":226},".\u002Fdocs\u002Fstorage",[156,228,229,232,233,208],{},[137,230,231],{},"Embed the screens."," Mount one handler in your server, extend the Nuxt\nlayer, and put a form beside what the setting affects. The screens take your\nNuxt UI theme. See ",[205,234,69],{"href":235},".\u002Fdocs\u002Fnuxt-layer",[156,237,238,241,242,208],{},[137,239,240],{},"Change it safely."," From the screen, the API or the terminal, every change\ngoes through the same checks and becomes a revision. See\n",[205,243,32],{"href":244},".\u002Fdocs\u002Fhistory-and-restore",[156,246,247,250,251,208],{},[137,248,249],{},"Let each workspace and person set their own, within the plan."," A field\nsays where it may be overridden and how far; a value is read through the\nsystem, the plan, the workspace and the person. See ",[205,252,40],{"href":253},".\u002Fdocs\u002Fscopes",[145,255,257],{"id":256},"principles","Principles",[133,259,260,261,265],{},"tunables is built around a few rules. The library enforces them where it can,\nand the ",[205,262,264],{"href":263},".\u002Fdocs\u002Fskills","skills"," check the rest.",[153,267,268,277,283,292,302,308,317,325,334,340,349,358],{},[156,269,270,273,274,208],{},[137,271,272],{},"One home per value."," A value is either a setting, stored in the database\nand tuned while the app runs, or a deployment value, read from the\nenvironment. Never both, so there is never a precedence rule. See\n",[205,275,48],{"href":276},".\u002Fdocs\u002Fdeployment-values",[156,278,279,282],{},[137,280,281],{},"Declare each setting once."," The definition is the only home of its\ndefault: no fallback literal at a read, no seed file, no copy in a migration.",[156,284,285,288,289,208],{},[137,286,287],{},"Saves are checked whole."," A change is checked field by field, then\nagainst the rules between settings, the presets the settings bound and the\nrecords your app keeps. When it is refused, nothing is written, and each\nproblem is said in plain words. See ",[205,290,28],{"href":291},".\u002Fdocs\u002Fchecks-and-guards",[156,293,294,297,298,301],{},[137,295,296],{},"Lowering a limit never breaks what uses it."," Whatever depends on a\nsetting is named by a rule, a guard or a preset's ",[216,299,300],{},"within",", and a change\nbelow it is refused, naming what is in the way.",[156,303,304,307],{},[137,305,306],{},"Every change is a revision",", with who, when, why, and each value before\nand after. Restoring one runs through the same checks as any other save.",[156,309,310,313,314,208],{},[137,311,312],{},"Presets are assigned by reference."," A named configuration, such as a\nplan or a cadence, has one default per slot; subjects follow the default or\nare assigned one by its key, never given a copy. See ",[205,315,36],{"href":316},".\u002Fdocs\u002Fpresets",[156,318,319,322,323,208],{},[137,320,321],{},"Layers are declared, never guessed."," Nothing is overridable unless its\nfield says where, and an override is bounded by what it inherits. Every\nscreen says where a value comes from, and a broader change that would leave\nan override outside its bound is refused, naming it. Layers live only in the\ndatabase, never between it and the environment. See ",[205,324,40],{"href":253},[156,326,327,330,331,208],{},[137,328,329],{},"Catalog entries in use cannot be retired."," A catalog holds allowed\nchoices, such as permitted models; an entry something still picks stays\navailable. See ",[205,332,44],{"href":333},".\u002Fdocs\u002Fcatalogs",[156,335,336,339],{},[137,337,338],{},"Boot never needs the database."," Creating tunables checks your definitions\nand touches nothing else.",[156,341,342,345,346,208],{},[137,343,344],{},"Access is decided by your app."," tunables has no users or roles. Your app\nsays who is asking and whether they may, on every request, down to the\nworkspace or person a request is about. See ",[205,347,61],{"href":348},".\u002Fdocs\u002Faccess",[156,350,351,354,355,208],{},[137,352,353],{},"The screens look like your app."," They are built on Nuxt UI and bring no\ncolors or fonts of their own. See ",[205,356,78],{"href":357},".\u002Fdocs\u002Ftheming",[156,359,360,363,364,370,371,374,375,208],{},[137,361,362],{},"Explanations come from the Product Model."," With a\n",[205,365,369],{"href":366,"rel":367},"https:\u002F\u002Fpdd-kit.dev",[368],"nofollow","pdd-kit"," Product Model, each field names the fact it\nis; its explanation is the model's own words, and ",[216,372,373],{},"tunables check"," fails when\na default disagrees with the one the model states. See\n",[205,376,87],{"href":377},".\u002Fdocs\u002Fcli-check",[145,379,381],{"id":380},"what-is-in-the-package","What is in the package",[383,384,385,398],"table",{},[386,387,388],"thead",{},[389,390,391,395],"tr",{},[392,393,394],"th",{},"Import",[392,396,397],{},"What it gives you",[399,400,401,412,426,436,453,463,473,483,493,503,516,526],"tbody",{},[389,402,403,409],{},[404,405,406],"td",{},[216,407,408],{},"tunables",[404,410,411],{},"Definitions, the engine, the in-memory store",[389,413,414,419],{},[404,415,416],{},[216,417,418],{},"tunables\u002Fpostgres",[404,420,421,422,219,424],{},"Postgres storage over ",[216,423,218],{},[216,425,222],{},[389,427,428,433],{},[404,429,430],{},[216,431,432],{},"tunables\u002Fdrizzle",[404,434,435],{},"The same tables as Drizzle definitions",[389,437,438,443],{},[404,439,440],{},[216,441,442],{},"tunables\u002Fserver",[404,444,445,446,449,450],{},"A fetch handler for any server that speaks ",[216,447,448],{},"Request"," and ",[216,451,452],{},"Response",[389,454,455,460],{},[404,456,457],{},[216,458,459],{},"tunables\u002Fexpress",[404,461,462],{},"Express middleware",[389,464,465,470],{},[404,466,467],{},[216,468,469],{},"tunables\u002Forpc",[404,471,472],{},"An oRPC router to nest in your own",[389,474,475,480],{},[404,476,477],{},[216,478,479],{},"tunables\u002Fclient",[404,481,482],{},"The client the screens call: over fetch or oRPC",[389,484,485,490],{},[404,486,487],{},[216,488,489],{},"tunables\u002Fvue",[404,491,492],{},"Headless Vue composables",[389,494,495,500],{},[404,496,497],{},[216,498,499],{},"tunables\u002Fnuxt",[404,501,502],{},"A Nuxt layer with ready screens on Nuxt UI",[389,504,505,510],{},[404,506,507],{},[216,508,509],{},"tunables\u002Fconfig",[404,511,512,515],{},[216,513,514],{},"tunables.config.ts"," for the CLI",[389,517,518,523],{},[404,519,520],{},[216,521,522],{},"tunables\u002Fpdd",[404,524,525],{},"Reading and checking a pdd-kit Product Model",[389,527,528,533],{},[404,529,530],{},[216,531,532],{},"tunables\u002Ftesting",[404,534,535],{},"In-memory tunables for your own tests",[133,537,538,539,542,543,546,547,551],{},"The ",[205,540,116],{"href":541},".\u002Fdocs\u002Fapi"," lists each export. ",[205,544,15],{"href":545},".\u002Fdocs\u002Fpatterns","\nnames the kinds of value tunables keeps, and\n",[205,548,550],{"href":549},".\u002Fdocs\u002Ftasklane-example","Tasklane"," walks through a complete example app's\nsettings.",[145,553,555],{"id":554},"next","Next",[133,557,558,559,563],{},"Start with the ",[205,560,562],{"href":561},".\u002Fdocs\u002Fquickstart","quickstart",": one group, from a constant to a\nscreen with a history, in your own app.",{"title":565,"searchDepth":566,"depth":566,"links":567},"",2,[568,569,570,571,572],{"id":147,"depth":566,"text":148},{"id":190,"depth":566,"text":191},{"id":256,"depth":566,"text":257},{"id":380,"depth":566,"text":381},{"id":554,"depth":566,"text":555},"md",{},true,1,{"title":6,"description":7},"docs\u002Findex","dwLtPrSuxykQ4I7fH9J13MDWylDCbWfq9OqH88fOt9k",1792845117971]