#4002
Puter.js types and docs drift audit - 2026-10-01
Full deep audit of puter.js types/docs vs implementation, pinned to commit fc62ea2ee12fc5fcc6d015342cf01cdce2ced56c.
Tags: 🟡 types · 🔵 docs · 🔴 both · 🐛 impl-bug · 🆕/🗑 manifest-drift. Findings were verified by per-family audit agents reading impl, JSDoc and docs. Low-confidence items are marked (low).
🆕 Manifest drift (the manifest is stale)
- No hand-written
types/dir.src/puter-js/types/**does not exist. Types are generated bytscfrom JSDoc insrc/(tsconfig.types.json), entrysrc/puter-js/index.d.ts. "Types" below means JSDoc andtypes.jstypedefs. - Impl layout is now
modules/<family>/forai, apps, events, hosting, kv, os, perms, teams, Email, FileSystem, networking.Auth.js, Drivers.js, FSItem.js, Peer.js, UI.js, Workers.jsremain top-level files. There is noPerms.js,KV.js,Hosting.js,Apps.js,AI.jsorOS.js. - 🆕 New families not in the manifest: Email (
modules/Email, docsEmail/), Events (modules/events, docsEvents/), Teams (modules/teams, docsTeams/). - 🆕 New FS ops:
getShareLink, getShares, listShared, listSharedByMe, share, unshare, shareUtil. New docs:FS/{getShareLink,getShares,listShared,listSharedByMe,share,unshare}. - 🆕 New docs:
Apps/checkName,Auth/{getProfile,getProfilePicture,updateProfile},Peer/createGuestGrant,Perms/{check,appData},UI/{exitPictureInPicture,requestPictureInPicture,showFeedbackDialog},Workers/dynamic. - 🆕 New Objects:
emaillistpage, emailmessage, emailmessageattachment, emailsummary, puterpeerconnection, puterpeerserver, ttsengine, ttsvoice, userprofile. - 🗑 Removed docs:
Perms/requestEmail,requestRead*andrequestWrite*,requestManage*(replaced byrequest()plus deprecated aliases).Auth/signInetc. still exist. Objects.mdindex omitscreateappresult, kvlistpage, signinresult, spaceinfo, userprofile(plus the Email and Peer objects).Auth/updateProfile.mdisdraft: trueand is not in the sidebar.
Highest-impact
- 🐛🔴 FS return type.
stat/readdir/copy/move/mkdir/rename/write/uploadare typed and documented asFSItem(with methods such asitem.read()), but the ops resolve raw snake_case v1 entries (is_dir,uid) with no methods. Thefsitem.mdexamples would throw. - 🐛🔴 Docs example
puter.space()does not exist. It isputer.fs.space()(FS/space.md:34). - 🐛 UI calls that hang or throw when used as documented.
socialShare(url[, msg])throws in IPC onoptions.left(IPC.js:380).showFontPicker('Arial')throws on a stringoptions(IPC.js:1528).alert()andprompt()with no message never resolve (IPC.js:280,300). - 🐛 UI options silently dropped in the app env.
showFontPicker.defaultFont(onlydefaultis read),alerticon/body_icon, andnotifytype/duration. - 🐛🔴 Nested
showSpinner/hideSpinner. Docs say calls are ref-counted, but there is no counter (UI.js:3011,3100). - 🔴 KV
MAX_VALUE_SIZEis399*1024, but docs say "400 KB". The check compares.length, not bytes (kv/lib/validate.js:3,18). - 🐛
workers.getLoggingHandle().close()is an unboundsocket.closeand throws (Workers.js:274). - 🐛
fs.stat({uid})withconsistency: 'eventual'uses cache keyitem:undefined, which can return another item's result (stat.js:51-56). - 🐛🔴
PuterPeerConnection.close(reason)is documented as delivering the reason to the peer, but it never does (Peer.js:775). - 🐛
'drain'is a typed Socket event that is never emitted (PSocket.js:24). A TLS handshake failure emitserrorbut no close, contrary to the docs (PTLS.js:69).
🐛 Impl-bug bucket
| Area | Item | Ref |
|---|---|---|
| UI | socialShare(url) / (url,msg) throws in IPC (options undefined) | UI.js:1229; IPC.js:380 |
| UI | showFontPicker(string) throws; defaultFont ignored (only default) | UI.js:1401-1407; IPC.js:1528; UIWindowFontPicker.js:47 |
| UI | alert icon/body_icon dropped; alert() and prompt() without message hang | IPC.js:280-304 |
| UI | notify type/duration dropped in the app env | IPC.js:320-344 |
| UI | requestPictureInPicture() with no url resolves /undefined; no TypeError as documented | UI.js:1482 |
| UI | Spinner has no nesting counter | UI.js:3011,3100 |
| UI | setWindow* with an unknown window_id leaves the promise pending (minor) | IPC.js:656-1260 |
| UI | toggleWindow, requestUpgrade, openDevPaymentsAccount have no GUI handler (hang or no-op) | UI.js:1805,1523,1200 |
| FS | Ops return raw entries, not FSItem | scaffold.js; mapV2EntryToV1.js |
| FS | FSItem stubs watch/open/setAsWallpaper/versions/trash/metadata are empty but typed | FSItem.js:196-290 |
| FS | stat cache key with uid only | stat.js:51-56,90 |
| FS | upload strict honoured only on the legacy path (minor) | legacyBatchUpload.js:325 |
| FS | getShares() with no path/uid sends ~/undefined instead of field_missing (minor) | getShares.js:28 |
| AI | ChatOptions.vision typed but never read from user params | chat.js:176-247 |
| Events | handlers.publishAll options typed HandlerOptions (includes replace) but only appUid is read | lib/handlers.js:82 |
| Workers | getLoggingHandle().close unbound; no lowercase and no #authenticateIfNeeded (low) | Workers.js:228-274 |
| Workers | WorkerDeployment.errors typed, but create throws on failure so it never resolves with errors | Workers.js:112 |
| Networking | 'drain' never emitted; TLS handshake failure emits no close; early TLSSocket.write throws | PSocket.js:24; PTLS.js:69,125 |
| Peer | close(reason) not sent to the peer; ensureTurnRelays rejects on a network throw despite "resolves either way" (low) | Peer.js:775,918 |
AI (modules/ai, docs AI/)
| Function | Item | Issue | Tag |
|---|---|---|---|
| chat | ChatOptions.vision | Typed but silently dropped | 🐛🟡 |
| chat | overloads | (prompt, testMode, options) and (prompt, media, testMode, options) are documented but not typed | 🟡 |
| chat | stream | ChatOptions has no stream. {stream:false} resolves to the AsyncIterable overload | 🟡 |
| chat | driver, response, context_management | Typed and forwarded; not in chat.md | 🔵 |
| ChatResponse | usage | Documented but missing in the typedef. choices is typed but undocumented | 🟡🔵 |
| ChatMessage | refusal | Typed, not in chatresponse.md | 🔵 |
| ImageContent | thoughtSignature | Used in the chat.md example; missing from the typedef | 🟡 |
| ChatMessage | {type:'file', puter_path} parts | Docs only; typedef is loose (nit) | 🟡 |
| listModels | return | Record<string, unknown>[]; docs promise id/provider etc. | 🟡 |
| img2txt | test_mode | Typed, docs list only testMode | 🔵 |
| img2txt | OcrAnnotationFormat | json_schema/jsonSchema and schema/schemaDefinition are tied in the typedef; docs say independent | 🟡 |
| txt2img | n typed but reserved; txt2img({prompt}, true) not in docs | nit | |
| txt2vid | driver | Missing from the options table | 🔵 |
| txt2speech | 3000-char limit | Docs/types say "less than 3000"; the impl allows exactly 3000 (tts.js:100) | 🔴 |
| listEngines/listVoices | speechify | Missing from the accepted-values lists and Objects | 🔵 |
| listVoices | listVoices(engine) | Missing from the Syntax block | 🔵 |
| speech2speech | file alias; camelCase and voice_id/model_id aliases | Not in docs | 🔵 |
| speech2speech | voice_settings | Docs say Object|String; typedef only Record | 🟡 |
| speech2txt | response_format | Exclude<string,'text'> collapses to string, so the return typing is wrong | 🟡 |
Apps / Auth / Perms / OS
| Function | Item | Issue | Tag |
|---|---|---|---|
| Objects/app.md | feedback_enabled | Typed, not documented | 🔵 |
| getUsers/users | user_email | Missing from the getUsers docs | 🔵 |
| create | return | Typed CreateAppResult without users/getUsers, which the impl adds | 🟡 |
| checkName | return | {name, available} not documented; no Apps.md tab | 🔵 |
| Auth.whoami | method | Public and typed, no docs page | 🔵 |
| Auth.getUser | unsigned | Throws synchronously with 401; callback forms undocumented | 🔵 |
| Objects/user.md | feature_flags, hasDevAccountAccess, otp; email_confirmed and requires_email_confirmation are boolean|number | Undocumented or wrong | 🔵 |
| Objects/monthlyusage.md | allowanceInfo.unit | Undocumented | 🔵 |
| Perms.request('email') | null return when granted with no email | Not documented | 🔵 |
| Perms.request('appData') | own app resolves true with no prompt; check() rejects on unknown app | Not documented | 🔵 |
| os.user / os.version | Typed, no docs family | 🔵 | |
| Perms | Deprecated aliases and grant/revoke are intentionally undocumented | note |
FS (FileSystem/**, docs FS/)
| Function | Item | Issue | Tag |
|---|---|---|---|
| FSItem | uid, uuid, isDirectory, readURL, writeURL, metadataURL | Typed, not in fsitem.md. fsitem.write data types are narrower in docs | 🔵 |
| FSItem | is_shared | Documented on FSItem; only present via FSItemRead | 🟡 |
| sign, readdirSubdomains | No docs page | 🔵 | |
| move | dedupeName/dedupe_name | Read and documented, missing from MoveOptionsOwn | 🟡 |
| read | cache | In neither types nor docs. byte_count "required with offset" is not enforced | 🔴 / 🔵 |
| readdir | no_subdomains | Missing from types and docs | 🔴 |
| readdir | consistency, no_thumbs, no_assocs | Undocumented. stream is not on ReaddirOptionsOwn | 🔵 / 🟡 |
| stat | consistency | Undocumented. stat(path) and legacy callbacks are not in the docs | 🔵 |
| write | createMissingAncestors, init, start, progress, abort | Undocumented | 🔵 |
| upload | name, parsedDataTransferItems, createFileParent, createMissingAncestors, shortcutTo, appUID, strict | Undocumented | 🔵 |
| upload | items | Impl accepts DataTransferItem(List), single File/Blob, string and mixed arrays; docs list fewer | 🔵 |
| upload | rejection shape | The signed path (main) rejects with partial, failedItems[], failedPaths, completedItemCount, totalItemCount; only the legacy UploadBatchError is typed | 🟡 |
| getReadURL | options-object and callback overloads untyped (minor) | 🟡 | |
| mkdir | aliases rename, recursive; shortcutTo undocumented (minor) | 🔵 | |
| share | recipients alias; {teamHandle}; return fields type, thumbnail, owner, holderTeam, issuedByApp undocumented. uid array is untyped | 🔵 / 🟡 | |
| unshare | impl reads paths, uid[], recipients, and array recipients; types and docs match each other but are narrower | 🟡🔵 (minor) | |
| getShares | holderTeam, anyone, pending missing from docs | 🔵 | |
| listShared | Docs list a narrower shape than Share | 🔵 (minor) | |
| getShareLink | options-form item is untyped (minor); docs hardcode puter.com | 🟡 |
KV / Hosting / Workers
| Function | Item | Issue | Tag |
|---|---|---|---|
| MAX_VALUE_SIZE | 399*1024; docs "400 KB" in set/update/add. Docs say bytes but the check uses .length (low) | 🔴 | |
| all ops | trailing optConfig {appUuid, disableSharing} | Typed and read everywhere; documented only partially (set.md) | 🔵 |
| set | optConfig forms (3-arg, 4-arg, object, batch+optConfig, {items, optConfig}) | Not in the Syntax block | 🔵 |
| flush | Docs say "None"; impl accepts optConfig and callbacks. kv.clear alias undocumented | 🔵 | |
| get/del/add/incr/decr/flush | Object forms (get({key}), add({key, pathAndValueMap})...) | Neither typed nor documented (low) | 🔴 |
| list | optConfig, top-level appUuid, trailing optConfig positions | Undocumented. fetchUntilFull "requires limit" is not enforced by the SDK | 🔵 |
| Hosting.create | dirPath | Docs say required; impl accepts create(subdomain), but the type does not | 🔴 |
| Hosting.update | dirPath | Omitted or empty disconnects the directory (root_dir:null); not documented | 🔵 |
| Hosting create/get/update/delete | Full host (x.puter.site) is stripped to the label | Undocumented | 🔵 |
| Hosting.list | Legacy callbacks undocumented. limit-only return is inconsistent with workers/list.md wording | 🔵 | |
| Workers.getLoggingHandle | No docs page | 🔵 | |
| Workers.create | Name lowercased; also reads and writes KV key user-workers; undocumented | 🔵 | |
| Workers/router.md | Missing router.custom, routing, handleCors; handler requestor and waitUntil | 🔵 | |
| Workers/types.md | Deprecated globals my, myself, puter_auth, puter_endpoint not flagged (low) | 🔵 |
Networking / Peer / Drivers
| Function | Item | Issue | Tag |
|---|---|---|---|
| net.generateWispV1URL | No docs page | 🔵 | |
| Socket/TLSSocket | 'drain' typed and never emitted | 🐛🟡 | |
| Socket.write | callback param undocumented | 🔵 | |
| Socket.addListener | Docs list 4 events; types allow more; tls* names never fire on plain Socket | 🔵 | |
| TLSSocket.on | Aliases data/open/close to tls* undocumented | 🔵 | |
| TLSSocket | Docs say close follows error; it doesn't. write before tlsopen throws | 🐛🔵 | |
| net.fetch | Input is typed RequestInfo|URL, docs say string. Rejects with plain strings. Always sends Connection: close and does not follow redirects | 🔵 | |
| Peer.close(reason) | Reason is never delivered to the peer | 🐛🔴 | |
| connect/serve | options.port is read by the impl; absent from types and docs (low) | 🔴 | |
| Peer.md | createGuestGrant missing from the Functions list. connect.md mentions only invite codes, but room names work | 🔵 | |
PuterPeerServer.start; PeerConnection createOffer etc. | Public and typed, undocumented (low, plumbing) | 🔵 | |
| AppConnection | close payload statusCode; response, appInstanceID, targetAppInstanceID, messageTarget undocumented | 🔵 | |
| Drivers.call | Loose ...unknown[]; puter.call is untyped; no docs page (low) | 🟡 |
Email / Events / Teams
| Function | Item | Issue | Tag |
|---|---|---|---|
| Email.send | No docs page. Docs and the class JSDoc wrongly call it the "previous name" of sendTransactional. options is typed Record<string, unknown>. EmailSendMethod has a stale "legacy alias" comment | 🔵🟡 | |
| Events.handlers.publishAll | options.replace ignored | 🐛🟡 | |
| Team typedef | directoryEnabled and require2fa descriptions are interleaved | 🟡 | |
| Teams list.md | require2fa missing from the Team table | 🔵 | |
| Teams.update | UpdateTeamAttributes lacks require2fa (internal type) | 🟡 | |
| Teams.listMembers | Public and typed; no docs page | 🔵 | |
| Teams list/listDirectory | Error table mentions a blank uid (cosmetic) | 🔵 |
UI (UI.js, src/gui/src/IPC.js, docs UI/)
| Function | Item | Issue | Tag |
|---|---|---|---|
| launchApp | background | JSDoc says it still appears in the taskbar; docs and impl say otherwise | 🟡 |
| launchApp | file_paths (godmode only, first path signed only); items (only [0] used); app_name, callback, "name#(as)pseudonym" | Undocumented | 🔵🟡 |
| prompt | 3rd-arg {defaultValue} | Typed, missing from docs | 🔵 |
| showOpenFilePicker | Inline param type ignores FilePickerOptions (path, accept: string|string[]). Docs type accept as String but show an array | 🟡🔵 | |
| showDirectoryPicker, prompt | Unused typedefs (cosmetic) | 🟡 | |
| connectToInstance, isWorkingActive, menubar() | No JSDoc or @internal; they leak into the types | 🟡 | |
| puter.exit | No JSDoc; non-number is coerced to 1 | 🟡 | |
| instancesOpen | Promise<unknown>; the GUI returns a count; no docs | 🟡🔵 | |
| setMenubar | MenuItem.id undocumented | 🔵 | |
| requestPermission | Typed and implemented; no docs anywhere | 🔵 | |
| enable/disableMenuItem, setMenuItemIcon, setMenuItemIconActive, setMenuItemChecked | Typed, with IPC handlers; no docs | 🔵 | |
| requestEmailConfirmation, getEntriesFromDataTransferItems, toggleWindow, requestUpgrade, openDevPaymentsAccount | No docs (some should be marked @internal) | 🔵 | |
| AppConnection | response.launchResult and launched, appInstanceID, appUid, privateAccess missing from AppConnection.md | 🔵 | |
| authenticateWithPuter | Returns undefined outside the web env; undocumented | 🔵 |
Utils / top-level wiring
| Function | Item | Issue | Tag |
|---|---|---|---|
| Utils.md | print blurb ("console") is wrong: it appends to document.body; env blurb says "environment variables" but it is a runtime string | 🔵 | |
| Utils.md | puter.exit, puter.getUser, puter.args, puter.appName, puter.authToken, puter.on/off (incl. auth.reauth_required), puter.drivers | Undocumented | 🔵 |
| appID.md | Can be undefined; URL-param and localStorage sources not mentioned (minor) | 🔵 | |
| puter.print / exit / getUser | No JSDoc, so the types are inferred and any | 🟡 | |
| Puter class | appID, appName, appDataPath, whoami, call are assigned dynamically and have no type declarations | 🟡 | |
| index.d.ts | Not exported: WorkerPage, TeamDirectoryEntry, UpgradePromptContext, the Peer event classes. No phantom exports were found | 🟡 | |
| Objects.md | 5+ pages are not in the index. /Objects/AppConnection/ casing (low) | 🔵 |
Activity
0No activity yet.