@@ -589,39 +589,6 @@ export interface ChannelInstructionsVersion {
589589 created_by : ChannelInstructionsUser | null ;
590590}
591591
592- export interface FolderInstructionsUser {
593- id ?: number ;
594- uuid ?: string ;
595- first_name ?: string ;
596- last_name ?: string | null ;
597- email ?: string ;
598- }
599-
600- export interface FolderInstructions {
601- id : string ;
602- content : string ;
603- version : number ;
604- is_latest : boolean ;
605- created_by : FolderInstructionsUser | null ;
606- created_at : string ;
607- updated_at : string ;
608- }
609-
610- export interface FolderInstructionsVersion {
611- id : string ;
612- version : number ;
613- is_latest : boolean ;
614- created_by : FolderInstructionsUser | null ;
615- created_at : string ;
616- }
617-
618- interface PaginatedFolderInstructionsVersions {
619- count : number ;
620- next : string | null ;
621- previous : string | null ;
622- results : FolderInstructionsVersion [ ] ;
623- }
624-
625592// Thrown when PUT /instructions/ rejects a publish because the caller's
626593// `base_version` is older than the current latest. Callers can re-fetch and
627594// retry against the new latest.
@@ -1561,298 +1528,6 @@ export class PostHogAPIClient {
15611528 ) ;
15621529 }
15631530
1564- // Desktop file system — the backend surface that backs canvas channels
1565- // (top-level folders) and dashboards. These routes aren't in the generated
1566- // OpenAPI client, so we use the raw fetcher.
1567- // Channels are top-level folders on the desktop file system. Filtering to
1568- // `type=folder` server-side (and requesting a large page) keeps us from
1569- // paginating over every dashboard and filed task just to populate the
1570- // sidebar channel list — the bulk of the initial-load cost otherwise.
1571- async getDesktopFileSystemChannels ( ) : Promise < Schemas . FileSystem [ ] > {
1572- const DESKTOP_FILE_SYSTEM_MAX_PAGES = 50 ;
1573- const DESKTOP_FILE_SYSTEM_PAGE_SIZE = 200 ;
1574- const teamId = await this . getTeamId ( ) ;
1575- const all : Schemas . FileSystem [ ] = [ ] ;
1576- let urlPath : string = `/api/projects/${ teamId } /desktop_file_system/?type=folder&limit=${ DESKTOP_FILE_SYSTEM_PAGE_SIZE } ` ;
1577- for ( let i = 0 ; i < DESKTOP_FILE_SYSTEM_MAX_PAGES ; i ++ ) {
1578- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1579- const response = await this . api . fetcher . fetch ( {
1580- method : "get" ,
1581- url,
1582- path : urlPath ,
1583- } ) ;
1584- if ( ! response . ok ) {
1585- throw new Error (
1586- `Failed to fetch desktop file system channels: ${ response . statusText } ` ,
1587- ) ;
1588- }
1589- const page = ( await response . json ( ) ) as Schemas . PaginatedFileSystemList ;
1590- all . push ( ...page . results ) ;
1591- if ( ! page . next ) return all ;
1592- const nextUrl = new URL ( page . next ) ;
1593- urlPath = `${ nextUrl . pathname } ${ nextUrl . search } ` ;
1594- }
1595- log . warn (
1596- `getDesktopFileSystemChannels hit MAX_PAGES (${ DESKTOP_FILE_SYSTEM_MAX_PAGES } ); returning partial results` ,
1597- { returned : all . length } ,
1598- ) ;
1599- return all ;
1600- }
1601-
1602- // Create a top-level channel (a folder row whose path is a single segment).
1603- async createDesktopFileSystemChannel (
1604- name : string ,
1605- ) : Promise < Schemas . FileSystem > {
1606- const teamId = await this . getTeamId ( ) ;
1607- const urlPath = `/api/projects/${ teamId } /desktop_file_system/` ;
1608- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1609- const response = await this . api . fetcher . fetch ( {
1610- method : "post" ,
1611- url,
1612- path : urlPath ,
1613- overrides : {
1614- body : JSON . stringify ( { path : name , type : "folder" , depth : 1 } ) ,
1615- } ,
1616- } ) ;
1617- if ( ! response . ok ) {
1618- throw new Error (
1619- `Failed to create desktop file system channel: ${ response . statusText } ` ,
1620- ) ;
1621- }
1622- return ( await response . json ( ) ) as Schemas . FileSystem ;
1623- }
1624-
1625- // Rename a top-level channel: PATCH its path (a single segment) to the new
1626- // name. The backend recomputes depth from the path.
1627- async renameDesktopFileSystemChannel (
1628- id : string ,
1629- name : string ,
1630- ) : Promise < Schemas . FileSystem > {
1631- const teamId = await this . getTeamId ( ) ;
1632- const urlPath = `/api/projects/${ teamId } /desktop_file_system/${ encodeURIComponent ( id ) } /` ;
1633- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1634- const response = await this . api . fetcher . fetch ( {
1635- method : "patch" ,
1636- url,
1637- path : urlPath ,
1638- overrides : {
1639- body : JSON . stringify ( { path : name } ) ,
1640- } ,
1641- } ) ;
1642- if ( ! response . ok ) {
1643- throw new Error (
1644- `Failed to rename desktop file system channel: ${ response . statusText } ` ,
1645- ) ;
1646- }
1647- return ( await response . json ( ) ) as Schemas . FileSystem ;
1648- }
1649-
1650- // Delete a desktop file system entry by id (used to remove top-level channels).
1651- async deleteDesktopFileSystem ( id : string ) : Promise < void > {
1652- const teamId = await this . getTeamId ( ) ;
1653- const urlPath = `/api/projects/${ teamId } /desktop_file_system/${ encodeURIComponent ( id ) } /` ;
1654- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1655- const response = await this . api . fetcher . fetch ( {
1656- method : "delete" ,
1657- url,
1658- path : urlPath ,
1659- } ) ;
1660- if ( ! response . ok && response . status !== 404 ) {
1661- throw new Error (
1662- `Failed to delete desktop file system channel: ${ response . statusText } ` ,
1663- ) ;
1664- }
1665- }
1666-
1667- // Desktop file system shortcuts — the user-scoped "starred" items on the
1668- // desktop surface (e.g. starred channels). Unlike the file system rows above,
1669- // shortcuts are per-user, so they back cross-device starring without leaking
1670- // one user's stars to their teammates. Not in the generated OpenAPI client,
1671- // so we use the raw fetcher.
1672- async getDesktopFileSystemShortcuts ( ) : Promise < Schemas . FileSystemShortcut [ ] > {
1673- const SHORTCUTS_MAX_PAGES = 50 ;
1674- const SHORTCUTS_PAGE_SIZE = 200 ;
1675- const teamId = await this . getTeamId ( ) ;
1676- const all : Schemas . FileSystemShortcut [ ] = [ ] ;
1677- let urlPath : string = `/api/projects/${ teamId } /desktop_file_system_shortcut/?limit=${ SHORTCUTS_PAGE_SIZE } ` ;
1678- for ( let i = 0 ; i < SHORTCUTS_MAX_PAGES ; i ++ ) {
1679- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1680- const response = await this . api . fetcher . fetch ( {
1681- method : "get" ,
1682- url,
1683- path : urlPath ,
1684- } ) ;
1685- if ( ! response . ok ) {
1686- throw new Error (
1687- `Failed to fetch desktop file system shortcuts: ${ response . statusText } ` ,
1688- ) ;
1689- }
1690- const page =
1691- ( await response . json ( ) ) as Schemas . PaginatedFileSystemShortcutList ;
1692- all . push ( ...page . results ) ;
1693- if ( ! page . next ) return all ;
1694- const nextUrl = new URL ( page . next ) ;
1695- urlPath = `${ nextUrl . pathname } ${ nextUrl . search } ` ;
1696- }
1697- log . warn (
1698- `getDesktopFileSystemShortcuts hit MAX_PAGES (${ SHORTCUTS_MAX_PAGES } ); returning partial results` ,
1699- { returned : all . length } ,
1700- ) ;
1701- return all ;
1702- }
1703-
1704- // Create a desktop shortcut for the current user. For a folder/channel the
1705- // backend links by `ref` (the folder's full path), with `path` as the label.
1706- async createDesktopFileSystemShortcut ( input : {
1707- path : string ;
1708- type : string ;
1709- ref ?: string ;
1710- href ?: string ;
1711- } ) : Promise < Schemas . FileSystemShortcut > {
1712- const teamId = await this . getTeamId ( ) ;
1713- const urlPath = `/api/projects/${ teamId } /desktop_file_system_shortcut/` ;
1714- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1715- const response = await this . api . fetcher . fetch ( {
1716- method : "post" ,
1717- url,
1718- path : urlPath ,
1719- overrides : {
1720- body : JSON . stringify ( input ) ,
1721- } ,
1722- } ) ;
1723- if ( ! response . ok ) {
1724- throw new Error (
1725- `Failed to create desktop file system shortcut: ${ response . statusText } ` ,
1726- ) ;
1727- }
1728- return ( await response . json ( ) ) as Schemas . FileSystemShortcut ;
1729- }
1730-
1731- // Delete a desktop shortcut by id (used to unstar). A 404 means it's already
1732- // gone, which is the desired end state, so we treat it as success.
1733- async deleteDesktopFileSystemShortcut ( id : string ) : Promise < void > {
1734- const teamId = await this . getTeamId ( ) ;
1735- const urlPath = `/api/projects/${ teamId } /desktop_file_system_shortcut/${ encodeURIComponent ( id ) } /` ;
1736- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1737- const response = await this . api . fetcher . fetch ( {
1738- method : "delete" ,
1739- url,
1740- path : urlPath ,
1741- } ) ;
1742- if ( ! response . ok && response . status !== 404 ) {
1743- throw new Error (
1744- `Failed to delete desktop file system shortcut: ${ response . statusText } ` ,
1745- ) ;
1746- }
1747- }
1748-
1749- // Per-folder, versioned markdown instructions for a desktop folder. The
1750- // endpoint is keyed on the FileSystem row id (must be `type === "folder"`).
1751- // Returns the current latest version or null when none has been published.
1752- async getDesktopFolderInstructions (
1753- folderId : string ,
1754- ) : Promise < FolderInstructions | null > {
1755- const teamId = await this . getTeamId ( ) ;
1756- const urlPath = `/api/projects/${ teamId } /desktop_file_system/${ encodeURIComponent ( folderId ) } /instructions/` ;
1757- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1758- const response = await this . api . fetcher . fetch ( {
1759- method : "get" ,
1760- url,
1761- path : urlPath ,
1762- } ) ;
1763- if ( response . status === 404 ) return null ;
1764- if ( ! response . ok ) {
1765- throw new Error (
1766- `Failed to fetch folder instructions: ${ response . statusText } ` ,
1767- ) ;
1768- }
1769- return ( await response . json ( ) ) as FolderInstructions ;
1770- }
1771-
1772- // Publish a new version of the folder's instructions. Pass `base_version`
1773- // (the latest version the editor was started from) for optimistic
1774- // concurrency; use 0 when no instructions exist yet. A 409 turns into a
1775- // typed `FolderInstructionsConflictError` so the UI can prompt to reload.
1776- async putDesktopFolderInstructions (
1777- folderId : string ,
1778- input : { content : string ; base_version ?: number } ,
1779- ) : Promise < FolderInstructions > {
1780- const teamId = await this . getTeamId ( ) ;
1781- const urlPath = `/api/projects/${ teamId } /desktop_file_system/${ encodeURIComponent ( folderId ) } /instructions/` ;
1782- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1783- const response = await this . api . fetcher . fetch ( {
1784- method : "put" ,
1785- url,
1786- path : urlPath ,
1787- overrides : {
1788- body : JSON . stringify ( input ) ,
1789- } ,
1790- } ) ;
1791- if ( response . status === 409 ) {
1792- throw new FolderInstructionsConflictError ( ) ;
1793- }
1794- if ( ! response . ok ) {
1795- throw new Error (
1796- `Failed to publish folder instructions: ${ response . statusText } ` ,
1797- ) ;
1798- }
1799- return ( await response . json ( ) ) as FolderInstructions ;
1800- }
1801-
1802- // Soft-delete all versions of this folder's instructions. The folder row
1803- // itself is not affected.
1804- async deleteDesktopFolderInstructions ( folderId : string ) : Promise < void > {
1805- const teamId = await this . getTeamId ( ) ;
1806- const urlPath = `/api/projects/${ teamId } /desktop_file_system/${ encodeURIComponent ( folderId ) } /instructions/` ;
1807- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1808- const response = await this . api . fetcher . fetch ( {
1809- method : "delete" ,
1810- url,
1811- path : urlPath ,
1812- } ) ;
1813- if ( ! response . ok && response . status !== 404 ) {
1814- throw new Error (
1815- `Failed to delete folder instructions: ${ response . statusText } ` ,
1816- ) ;
1817- }
1818- }
1819-
1820- // List version metadata (no content) newest-first. Single page is enough for
1821- // the typical UI; we cap follow-up pages to avoid runaway pagination on
1822- // pathological histories.
1823- async listDesktopFolderInstructionVersions (
1824- folderId : string ,
1825- ) : Promise < FolderInstructionsVersion [ ] > {
1826- const VERSIONS_MAX_PAGES = 20 ;
1827- const teamId = await this . getTeamId ( ) ;
1828- const all : FolderInstructionsVersion [ ] = [ ] ;
1829- let urlPath = `/api/projects/${ teamId } /desktop_file_system/${ encodeURIComponent ( folderId ) } /instructions/versions/` ;
1830- for ( let i = 0 ; i < VERSIONS_MAX_PAGES ; i ++ ) {
1831- const url = new URL ( `${ this . api . baseUrl } ${ urlPath } ` ) ;
1832- const response = await this . api . fetcher . fetch ( {
1833- method : "get" ,
1834- url,
1835- path : urlPath ,
1836- } ) ;
1837- if ( ! response . ok ) {
1838- throw new Error (
1839- `Failed to fetch folder instruction versions: ${ response . statusText } ` ,
1840- ) ;
1841- }
1842- const page =
1843- ( await response . json ( ) ) as PaginatedFolderInstructionsVersions ;
1844- all . push ( ...page . results ) ;
1845- if ( ! page . next ) return all ;
1846- const nextUrl = new URL ( page . next ) ;
1847- urlPath = `${ nextUrl . pathname } ${ nextUrl . search } ` ;
1848- }
1849- log . warn (
1850- `listDesktopFolderInstructionVersions hit MAX_PAGES (${ VERSIONS_MAX_PAGES } ); returning partial results` ,
1851- { folderId, returned : all . length } ,
1852- ) ;
1853- return all ;
1854- }
1855-
18561531 // The task currently generating this folder's CONTEXT.md, shared across the
18571532 // project so any user sees an in-progress generation (instead of fragile
18581533 // local state). Keyed on the folder row (which always exists), not the
0 commit comments