[{"data":1,"prerenderedAt":1139},["ShallowReactive",2],{"docs-/docs/development/custom-views":3},{"id":4,"title":5,"body":6,"description":1132,"extension":1133,"meta":1134,"navigation":205,"path":1135,"seo":1136,"stem":1137,"__hash__":1138},"docs/docs/development/custom-views.md","Custom views",{"type":7,"value":8,"toc":1120},"minimark",[9,13,39,44,54,61,65,141,152,156,167,525,532,681,695,702,711,805,824,875,879,882,974,997,1005,1014,1018,1025,1052,1063,1067,1116],[10,11,5],"h1",{"id":12},"custom-views",[14,15,16,17,21,22,26,27,30,31,34,35,38],"p",{},"Declared fields cover most funcs, but sometimes you want to own every pixel. A ",[18,19,20],"strong",{},"CUSTOM"," func\nreplaces ",[23,24,25],"code",{},"fields.input"," / ",[23,28,29],{},"fields.output"," with a frontend you write yourself: a ",[23,32,33],{},"view.tsx","\nthat renders the whole UI and calls your backend ",[23,36,37],{},"process()"," like a function.",[40,41,43],"h2",{"id":42},"file-layout","File layout",[45,46,51],"pre",{"className":47,"code":49,"language":50},[48],"language-text","my-func/\n├── artifuncs.json       # \"type\": \"CUSTOM\", \"view\": \"view/view.tsx\"\n├── main.py              # backend — process(input, settings) -> dict\n├── requirements.txt     # backend deps (pip)\n└── view/\n    ├── view.tsx         # the whole frontend UI (entry point)\n    └── package.json     # frontend deps (pnpm)\n","text",[23,52,49],{"__ignoreMap":53},"",[14,55,56,57,60],{},"Everything frontend lives under ",[23,58,59],{},"view/","; the root stays backend-only.",[40,62,64],{"id":63},"artifuncsjson","artifuncs.json",[45,66,70],{"className":67,"code":68,"language":69,"meta":53,"style":53},"language-json shiki shiki-themes github-light github-dark","{\n  \"name\": \"SHA-256\",\n  \"type\": \"CUSTOM\",\n  \"language\": \"PYTHON\",\n  \"view\": \"view/view.tsx\"\n}\n","json",[23,71,72,81,98,111,124,135],{"__ignoreMap":53},[73,74,77],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sVt8B","{\n",[73,82,84,88,91,95],{"class":75,"line":83},2,[73,85,87],{"class":86},"sj4cs","  \"name\"",[73,89,90],{"class":79},": ",[73,92,94],{"class":93},"sZZnC","\"SHA-256\"",[73,96,97],{"class":79},",\n",[73,99,101,104,106,109],{"class":75,"line":100},3,[73,102,103],{"class":86},"  \"type\"",[73,105,90],{"class":79},[73,107,108],{"class":93},"\"CUSTOM\"",[73,110,97],{"class":79},[73,112,114,117,119,122],{"class":75,"line":113},4,[73,115,116],{"class":86},"  \"language\"",[73,118,90],{"class":79},[73,120,121],{"class":93},"\"PYTHON\"",[73,123,97],{"class":79},[73,125,127,130,132],{"class":75,"line":126},5,[73,128,129],{"class":86},"  \"view\"",[73,131,90],{"class":79},[73,133,134],{"class":93},"\"view/view.tsx\"\n",[73,136,138],{"class":75,"line":137},6,[73,139,140],{"class":79},"}\n",[14,142,143,144,147,148,151],{},"No ",[23,145,146],{},"fields"," — the view owns the UI. ",[23,149,150],{},"settings"," still works and is readable from the view.",[40,153,155],{"id":154},"the-view","The view",[14,157,158,159,162,163,166],{},"A default-exported React-style component. Import from ",[23,160,161],{},"\"react\""," (a light Preact runtime under\nthe hood — hooks work as usual) and ",[23,164,165],{},"\"@artifuncs/sdk\"",":",[45,168,172],{"className":169,"code":170,"language":171,"meta":53,"style":53},"language-tsx shiki shiki-themes github-light github-dark","import { useState } from \"react\"\nimport { useArtifuncs } from \"@artifuncs/sdk\"\n\nexport default function View() {\n  const { run } = useArtifuncs()\n  const [text, setText] = useState(\"\")\n  const [result, setResult] = useState\u003C{ hash: string } | null>(null)\n\n  return (\n    \u003Cdiv className=\"grid gap-3 p-4\">\n      \u003Ctextarea className=\"rounded border border-rule bg-surface p-2 text-ink\"\n        value={text} onChange={e => setText(e.target.value)} />\n      \u003Cbutton className=\"rounded bg-brand px-4 py-2 text-brand-ink\"\n        onClick={async () => setResult(await run({ text }))}>\n        Hash it\n      \u003C/button>\n      {result && \u003Ccode className=\"rounded bg-surface-2 p-2 font-mono text-ink\">{result.hash}\u003C/code>}\n    \u003C/div>\n  )\n}\n","tsx",[23,173,174,189,201,207,225,248,280,329,334,343,364,380,411,426,459,465,475,504,514,520],{"__ignoreMap":53},[73,175,176,180,183,186],{"class":75,"line":76},[73,177,179],{"class":178},"szBVR","import",[73,181,182],{"class":79}," { useState } ",[73,184,185],{"class":178},"from",[73,187,188],{"class":93}," \"react\"\n",[73,190,191,193,196,198],{"class":75,"line":83},[73,192,179],{"class":178},[73,194,195],{"class":79}," { useArtifuncs } ",[73,197,185],{"class":178},[73,199,200],{"class":93}," \"@artifuncs/sdk\"\n",[73,202,203],{"class":75,"line":100},[73,204,206],{"emptyLinePlaceholder":205},true,"\n",[73,208,209,212,215,218,222],{"class":75,"line":113},[73,210,211],{"class":178},"export",[73,213,214],{"class":178}," default",[73,216,217],{"class":178}," function",[73,219,221],{"class":220},"sScJk"," View",[73,223,224],{"class":79},"() {\n",[73,226,227,230,233,236,239,242,245],{"class":75,"line":126},[73,228,229],{"class":178},"  const",[73,231,232],{"class":79}," { ",[73,234,235],{"class":86},"run",[73,237,238],{"class":79}," } ",[73,240,241],{"class":178},"=",[73,243,244],{"class":220}," useArtifuncs",[73,246,247],{"class":79},"()\n",[73,249,250,252,255,257,260,263,266,268,271,274,277],{"class":75,"line":137},[73,251,229],{"class":178},[73,253,254],{"class":79}," [",[73,256,50],{"class":86},[73,258,259],{"class":79},", ",[73,261,262],{"class":86},"setText",[73,264,265],{"class":79},"] ",[73,267,241],{"class":178},[73,269,270],{"class":220}," useState",[73,272,273],{"class":79},"(",[73,275,276],{"class":93},"\"\"",[73,278,279],{"class":79},")\n",[73,281,283,285,287,290,292,295,297,299,301,304,308,310,313,315,318,321,324,327],{"class":75,"line":282},7,[73,284,229],{"class":178},[73,286,254],{"class":79},[73,288,289],{"class":86},"result",[73,291,259],{"class":79},[73,293,294],{"class":86},"setResult",[73,296,265],{"class":79},[73,298,241],{"class":178},[73,300,270],{"class":220},[73,302,303],{"class":79},"\u003C{ ",[73,305,307],{"class":306},"s4XuR","hash",[73,309,166],{"class":178},[73,311,312],{"class":86}," string",[73,314,238],{"class":79},[73,316,317],{"class":178},"|",[73,319,320],{"class":86}," null",[73,322,323],{"class":79},">(",[73,325,326],{"class":86},"null",[73,328,279],{"class":79},[73,330,332],{"class":75,"line":331},8,[73,333,206],{"emptyLinePlaceholder":205},[73,335,337,340],{"class":75,"line":336},9,[73,338,339],{"class":178},"  return",[73,341,342],{"class":79}," (\n",[73,344,346,349,353,356,358,361],{"class":75,"line":345},10,[73,347,348],{"class":79},"    \u003C",[73,350,352],{"class":351},"s9eBZ","div",[73,354,355],{"class":220}," className",[73,357,241],{"class":178},[73,359,360],{"class":93},"\"grid gap-3 p-4\"",[73,362,363],{"class":79},">\n",[73,365,367,370,373,375,377],{"class":75,"line":366},11,[73,368,369],{"class":79},"      \u003C",[73,371,372],{"class":351},"textarea",[73,374,355],{"class":220},[73,376,241],{"class":178},[73,378,379],{"class":93},"\"rounded border border-rule bg-surface p-2 text-ink\"\n",[73,381,383,386,388,391,394,396,399,402,405,408],{"class":75,"line":382},12,[73,384,385],{"class":220},"        value",[73,387,241],{"class":178},[73,389,390],{"class":79},"{text} ",[73,392,393],{"class":220},"onChange",[73,395,241],{"class":178},[73,397,398],{"class":79},"{",[73,400,401],{"class":306},"e",[73,403,404],{"class":178}," =>",[73,406,407],{"class":220}," setText",[73,409,410],{"class":79},"(e.target.value)} />\n",[73,412,414,416,419,421,423],{"class":75,"line":413},13,[73,415,369],{"class":79},[73,417,418],{"class":351},"button",[73,420,355],{"class":220},[73,422,241],{"class":178},[73,424,425],{"class":93},"\"rounded bg-brand px-4 py-2 text-brand-ink\"\n",[73,427,429,432,434,436,439,442,445,448,450,453,456],{"class":75,"line":428},14,[73,430,431],{"class":220},"        onClick",[73,433,241],{"class":178},[73,435,398],{"class":79},[73,437,438],{"class":178},"async",[73,440,441],{"class":79}," () ",[73,443,444],{"class":178},"=>",[73,446,447],{"class":220}," setResult",[73,449,273],{"class":79},[73,451,452],{"class":178},"await",[73,454,455],{"class":220}," run",[73,457,458],{"class":79},"({ text }))}>\n",[73,460,462],{"class":75,"line":461},15,[73,463,464],{"class":79},"        Hash it\n",[73,466,468,471,473],{"class":75,"line":467},16,[73,469,470],{"class":79},"      \u003C/",[73,472,418],{"class":351},[73,474,363],{"class":79},[73,476,478,481,484,487,489,491,493,496,499,501],{"class":75,"line":477},17,[73,479,480],{"class":79},"      {result ",[73,482,483],{"class":178},"&&",[73,485,486],{"class":79}," \u003C",[73,488,23],{"class":351},[73,490,355],{"class":220},[73,492,241],{"class":178},[73,494,495],{"class":93},"\"rounded bg-surface-2 p-2 font-mono text-ink\"",[73,497,498],{"class":79},">{result.hash}\u003C/",[73,500,23],{"class":351},[73,502,503],{"class":79},">}\n",[73,505,507,510,512],{"class":75,"line":506},18,[73,508,509],{"class":79},"    \u003C/",[73,511,352],{"class":351},[73,513,363],{"class":79},[73,515,517],{"class":75,"line":516},19,[73,518,519],{"class":79},"  )\n",[73,521,523],{"class":75,"line":522},20,[73,524,140],{"class":79},[40,526,528,529],{"id":527},"the-sdk-useartifuncs","The SDK — ",[23,530,531],{},"useArtifuncs()",[533,534,535,548],"table",{},[536,537,538],"thead",{},[539,540,541,545],"tr",{},[542,543,544],"th",{},"Member",[542,546,547],{},"Description",[549,550,551,582,611,636,650,671],"tbody",{},[539,552,553,559],{},[554,555,556],"td",{},[23,557,558],{},"run(input)",[554,560,561,562,565,566,568,569,259,572,575,576,578,579,581],{},"Calls ",[23,563,564],{},"process(input, settings)"," in the sandbox. Returns a Promise of the exact dict ",[23,567,37],{}," returned. Rejects with a typed error (",[23,570,571],{},"e.type",[23,573,574],{},"e.message",") when ",[23,577,37],{}," raises. Settings are attached automatically — ",[23,580,235],{}," takes input only.",[539,583,584,588],{},[554,585,586],{},[23,587,150],{},[554,589,590,591,594,595,598,599,602,603,606,607,610],{},"Settings API: ",[23,592,593],{},"settings.get(key, fallback?)"," (effective value: view-written > instance > declared default), ",[23,596,597],{},"settings.set(key, value)"," (visible to the next ",[23,600,601],{},"run()","; persists on board instances), ",[23,604,605],{},"settings.all()"," (snapshot dict), ",[23,608,609],{},"settings.open()"," (opens the tool's settings panel).",[539,612,613,618],{},[554,614,615],{},[23,616,617],{},"on(event, cb)",[554,619,620,621,624,625,628,629,624,632,635],{},"Subscribe to view events; returns an unsubscribe function. ",[23,622,623],{},"\"settings.valueChanged\""," → ",[23,626,627],{},"cb(key, oldValue, newValue)"," fires when a setting changes (e.g. the user edits the settings panel — the view is NOT remounted; react in the handler). ",[23,630,631],{},"\"theme.changed\"",[23,633,634],{},"cb(theme)"," on light/dark switches.",[539,637,638,643],{},[554,639,640],{},[23,641,642],{},"theme.get(token)",[554,644,645,646,649],{},"Resolved theme token value, e.g. ",[23,647,648],{},"theme.get(\"brand\")"," → the brand color — for canvas / chart code that needs real values in JS.",[539,651,652,657],{},[554,653,654],{},[23,655,656],{},"onLog(cb)",[554,658,659,660,663,664,666,667,670],{},"Live log stream: every ",[23,661,662],{},"print()"," in ",[23,665,37],{}," arrives as ",[23,668,669],{},"{ level, message, timestamp }",". Returns an unsubscribe function.",[539,672,673,678],{},[554,674,675],{},[23,676,677],{},"files",[554,679,680],{},"Run-scoped file API — see below.",[14,682,683,684,687,688,691,692,694],{},"Multiple backend operations? Branch on a key: ",[23,685,686],{},"run({ action: \"hash\", text })"," and ",[23,689,690],{},"switch"," in\n",[23,693,37],{},".",[40,696,698,699],{"id":697},"files-ctxfiles","Files — ",[23,700,701],{},"ctx.files",[14,703,704,705,707,708,710],{},"All file operations share the view's run scope, so uploads are visible to ",[23,706,37],{}," and\neverything ",[23,709,37],{}," writes is fetchable back:",[533,712,713,721],{},[536,714,715],{},[539,716,717,719],{},[542,718,544],{},[542,720,547],{},[549,722,723,752,765,778,795],{},[539,724,725,730],{},[554,726,727],{},[23,728,729],{},"files.upload(file, { onProgress }?)",[554,731,732,733,736,737,740,741,744,745,747,748,751],{},"Upload a browser ",[23,734,735],{},"File"," eagerly. Resolves ",[23,738,739],{},"{ name, size }","; pass ",[23,742,743],{},"name"," in a later ",[23,746,601],{}," input. ",[23,749,750],{},"onProgress(loaded, total)"," for progress bars.",[539,753,754,759],{},[554,755,756],{},[23,757,758],{},"files.text(name)",[554,760,761,762,764],{},"Read a file ",[23,763,37],{}," wrote, as a string.",[539,766,767,772],{},[554,768,769],{},[23,770,771],{},"files.blob(name)",[554,773,774,775,694],{},"Read as a ",[23,776,777],{},"Blob",[539,779,780,785],{},[554,781,782],{},[23,783,784],{},"files.url(name)",[554,786,787,788,26,791,794],{},"Object URL — drop straight into ",[23,789,790],{},"\u003Cimg src>",[23,792,793],{},"\u003Ca href>",". Auto-revoked when the view unmounts.",[539,796,797,802],{},[554,798,799],{},[23,800,801],{},"files.save(name, saveAs?)",[554,803,804],{},"Trigger a browser download.",[14,806,807,810,811,814,815,817,818,820,821,823],{},[18,808,809],{},"The shortcut",": you usually don't need ",[23,812,813],{},"upload()"," at all — any ",[23,816,735],{}," object you pass in a\n",[23,819,601],{}," input is uploaded automatically and replaced with its filename, so ",[23,822,37],{},"\nreceives the standard filename string:",[45,825,827],{"className":169,"code":826,"language":171,"meta":53,"style":53},"const out = await run({ upload: file })          // file is a File from \u003Caf-file-field>\nconst imgSrc = await files.url(out.result.path)  // show what process() generated\n",[23,828,829,852],{"__ignoreMap":53},[73,830,831,834,837,840,843,845,848],{"class":75,"line":76},[73,832,833],{"class":178},"const",[73,835,836],{"class":86}," out",[73,838,839],{"class":178}," =",[73,841,842],{"class":178}," await",[73,844,455],{"class":220},[73,846,847],{"class":79},"({ upload: file })          ",[73,849,851],{"class":850},"sJ8bj","// file is a File from \u003Caf-file-field>\n",[73,853,854,856,859,861,863,866,869,872],{"class":75,"line":83},[73,855,833],{"class":178},[73,857,858],{"class":86}," imgSrc",[73,860,839],{"class":178},[73,862,842],{"class":178},[73,864,865],{"class":79}," files.",[73,867,868],{"class":220},"url",[73,870,871],{"class":79},"(out.result.path)  ",[73,873,874],{"class":850},"// show what process() generated\n",[40,876,878],{"id":877},"styling","Styling",[14,880,881],{},"Tailwind utilities are compiled for your view automatically — no config, no install. Color,\nradius and font utilities are mapped to the app's theme tokens, so your view matches the app\nand follows light/dark mode with zero effort:",[883,884,885,901,916,931,940,955],"ul",{},[886,887,888,889,259,892,259,895,259,898],"li",{},"Surfaces: ",[23,890,891],{},"bg-bg",[23,893,894],{},"bg-surface",[23,896,897],{},"bg-surface-2",[23,899,900],{},"bg-surface-3",[886,902,903,904,259,907,259,910,259,913],{},"Text: ",[23,905,906],{},"text-ink",[23,908,909],{},"text-ink-2",[23,911,912],{},"text-ink-3",[23,914,915],{},"text-ink-4",[886,917,918,919,259,922,259,925,259,928],{},"Brand: ",[23,920,921],{},"bg-brand",[23,923,924],{},"text-brand-ink",[23,926,927],{},"bg-brand-soft",[23,929,930],{},"hover:bg-brand-hover",[886,932,933,934,259,937],{},"Borders: ",[23,935,936],{},"border-rule",[23,938,939],{},"border-rule-2",[886,941,942,943,946,947,950,951,954],{},"Semantics: ",[23,944,945],{},"bg-info|success|warn|danger"," (+ ",[23,948,949],{},"-soft"," backgrounds, ",[23,952,953],{},"text-*-ink"," text)",[886,956,957,958,259,961,259,964,967,968,259,971],{},"Radius: ",[23,959,960],{},"rounded-sm",[23,962,963],{},"rounded",[23,965,966],{},"rounded-lg"," · Fonts: ",[23,969,970],{},"font-sans",[23,972,973],{},"font-mono",[14,975,976,977,259,980,259,983,259,986,259,989,992,993,996],{},"Standard Tailwind spacing/layout (",[23,978,979],{},"p-4",[23,981,982],{},"gap-2",[23,984,985],{},"grid",[23,987,988],{},"flex",[23,990,991],{},"text-sm",", …) all work. Raw\nCSS with ",[23,994,995],{},"var(--surface)"," etc. works too.",[14,998,999,1000,694],{},"For ready-made buttons, inputs, cards, alerts and badges, use the host's component classes —\nsee the ",[1001,1002,1004],"a",{"href":1003},"/docs/development/view-components","view components API",[14,1006,1007,1010,1011,1013],{},[18,1008,1009],{},"Your view mounts edge-to-edge — there is no default padding around it."," Field-based funcs\nget a 16px content inset from the host; CUSTOM views don't, so you own every pixel of the\ntile. Want breathing room? Add it yourself (e.g. a ",[23,1012,979],{}," wrapper, like the starter template).\nWant a full-bleed canvas, map, or editor? Just don't.",[40,1015,1017],{"id":1016},"frontend-dependencies","Frontend dependencies",[14,1019,1020,1021,1024],{},"Declare in ",[23,1022,1023],{},"view/package.json"," — installed with pnpm and bundled into your view on save:",[45,1026,1028],{"className":67,"code":1027,"language":69,"meta":53,"style":53},"{ \"dependencies\": { \"chart.js\": \"^4.4.1\" } }\n",[23,1029,1030],{"__ignoreMap":53},[73,1031,1032,1035,1038,1041,1044,1046,1049],{"class":75,"line":76},[73,1033,1034],{"class":79},"{ ",[73,1036,1037],{"class":86},"\"dependencies\"",[73,1039,1040],{"class":79},": { ",[73,1042,1043],{"class":86},"\"chart.js\"",[73,1045,90],{"class":79},[73,1047,1048],{"class":93},"\"^4.4.1\"",[73,1050,1051],{"class":79}," } }\n",[14,1053,1054,1055,1058,1059,1062],{},"React itself is provided by the platform — don't list it. Node-native packages can't run in\nthe browser; put that logic in ",[23,1056,1057],{},"main.py"," (with its deps in ",[23,1060,1061],{},"requirements.txt",").",[40,1064,1066],{"id":1065},"workflow","Workflow",[883,1068,1069,1077,1101,1113],{},[886,1070,1071,90,1074],{},[18,1072,1073],{},"Scaffold",[23,1075,1076],{},"artifuncs init my-func --custom",[886,1078,1079,1082,1083,1086,1087,1090,1091,1094,1095,1097,1098,694],{},[18,1080,1081],{},"Web IDE",": edit ",[23,1084,1085],{},"view/view.tsx"," → save → the preview rebuilds and remounts live. The\npreview header's ",[18,1088,1089],{},"Settings"," button (also ",[23,1092,1093],{},"ctx.settings.open()",") edits the settings\ndeclared in ",[23,1096,64],{}," — values feed executions and fire ",[23,1099,1100],{},"settings.valueChanged",[886,1102,1103,90,1106,1109,1110,1112],{},[18,1104,1105],{},"CLI",[23,1107,1108],{},"artifuncs dev"," syncs your local folder; saving any file under ",[23,1111,59],{}," triggers a\nsandbox rebuild and the open preview hot-reloads.",[886,1114,1115],{},"Build errors (syntax error in the view, missing import) appear in the log panel and the\npreview shows a build-failed state.",[1117,1118,1119],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":53,"searchDepth":83,"depth":83,"links":1121},[1122,1123,1124,1125,1127,1129,1130,1131],{"id":42,"depth":83,"text":43},{"id":63,"depth":83,"text":64},{"id":154,"depth":83,"text":155},{"id":527,"depth":83,"text":1126},"The SDK — useArtifuncs()",{"id":697,"depth":83,"text":1128},"Files — ctx.files",{"id":877,"depth":83,"text":878},{"id":1016,"depth":83,"text":1017},{"id":1065,"depth":83,"text":1066},"Build a fully custom interactive UI for your func with view.tsx — React-style components, Tailwind styling with theme tokens, and the @artifuncs/sdk.","md",{},"/docs/development/custom-views",{"title":5,"description":1132},"docs/development/custom-views","m5oYdfa8Yph_szhvxEC77L-dU2hTjDKsHC_rc1TjGxo",1784586441671]