# API Overview - Product v1.0.0



## Control Architecture

UnifiedFiler Product v1.0.0 provides Host-facing Control classes. The Host talks to the Control; the Control owns the internal jQuery Plugin view/controller components.

```text
Host
  -> FileExplorerControl / FilePickerControl / FileSaverControl / UnifiedFilerControl
     -> CommandRegistry / StateStore / EventBus / Service / Adapter
        -> internal jQuery Plugin View / Controller
```

### FileExplorerControl

```javascript
var control = new UnifiedFiler.controls.FileExplorerControl('#explorer', {
    services: demoContext.services(),
    defaultStorage: 'appStorage',
    storageTypes: ['appStorage', 'localDisk', 'backendApi'],
    showTree: true,
    viewMode: 'detail',
    onEvent: function (name, event) {
        console.log(name, event.state || event);
    }
});

control.mount().then(function () {
    control.exec('refresh');
    control.exec('newFolder');
    control.openFolder('/Documents');
});

control.on('selectionChanged', function (event) {
    console.log(event.entries);
});
control.on('fileOpened', function (event) {
    console.log(event.entry);
});
```

Public methods:

```javascript
control.mount()
control.destroy()
control.exec(commandId, args)
control.canExec(commandId)
control.getCommandState(commandId)
control.registerCommand(command)
control.openFolder(pathOrEntry, adapterId)
control.openPath(adapterId, path)
control.refresh(options)
control.setStorage(adapterId)
control.setViewMode(viewMode)
control.getSelection()
control.clearSelection()
control.startInlineRename(entry)
control.getState()
control.getView()              // advanced diagnostic access to internal jQuery Plugin instance
control.getCommandRegistry()
control.on(eventName, handler)
control.off(eventName, handler)
```

Built-in command ids include:

```text
refresh, up, back, forward, newFolder, newFile, upload,
rename, delete, paste, toggleFavorite, togglePreview,
showRecent, showFavorites
```

### FilePickerControl / FileSaverControl

```javascript
var picker = new UnifiedFiler.controls.FilePickerControl({
    services: demoContext.services(),
    defaultStorage: 'appStorage',
    selectionType: 'file',
    showFolderTree: true,
    treeStorageMode: 'active'
});
picker.mount();

var saver = new UnifiedFiler.controls.FileSaverControl({
    services: demoContext.services(),
    defaultStorage: 'appStorage',
    defaultFileName: 'document.md',
    showFolderTree: true
});
saver.mount();
```

### UnifiedFilerControl factory

```javascript
var filer = new UnifiedFiler.controls.UnifiedFilerControl({
    services: demoContext.services(),
    defaultStorage: 'appStorage',
    storageTypes: ['appStorage', 'localDisk', 'backendApi']
});

var explorer = filer.createExplorer('#explorer', { showTree: true });
var picker = filer.createPicker({ showFolderTree: true });
var saver = filer.createSaver({ showFolderTree: true, defaultFileName: 'untitled.txt' });
```

The older direct jQuery Plugin API remains available for compatibility, but Host integration should prefer Control classes for new implementations.

## PackageInfoService v1.6+

```javascript
var packageInfoService = new UnifiedFiler.services.PackageInfoService();
packageInfoService.isPackage(entry);
packageInfoService.inspect(fileService, entry, {
    maxPackageSize: 20 * 1024 * 1024
}).then(function (packageInfo) {
    console.log(packageInfo.manifest, packageInfo.entries);
});
packageInfoService.render(fileService, entry, {
    maxEntries: 60
}).then(function (html) {
    previewPane.innerHTML = html;
});
```

Supported package extensions are `.zip`, `.uwpsx`, `.usldx`, `.umal`, `.umdlx`, and `.ualbx`. JSZip must be loaded globally by the Host when package inspection is used.

FileExplorer v1.6+ exposes package helpers.

```javascript
$('#fileExplorer').fileExplorer('getPackageInfo', entry).then(function (info) {
    console.log(info);
});
$('#fileExplorer').fileExplorer('showPackageInfo', entry);
```


## GoogleDriveAdapter v1.8.1

`GoogleDriveAdapter` exposes Google Drive through the same StorageAdapter contract as App Storage, This Device, and Backend API. In v1.8.1 the default implementation is browser-only: Google Identity Services provides the OAuth token, Google Drive API v3 is called directly through `fetch`, and Google Picker can be opened from the adapter. No proprietary backend proxy is required.

```javascript
var googleDrive = new UnifiedFiler.adapters.GoogleDriveAdapter({
    id: 'googleDrive',
    label: 'Google Drive',
    tenantId: activeTenantId,
    appId: '<Google App ID>',
    userId: currentUserId,
    clientId: '<OAuth Client ID>',
    apiKey: '<API Key>',
    scopes: ['https://www.googleapis.com/auth/drive.file'],
    enablePicker: true,
    supportsAllDrives: true,
    includeItemsFromAllDrives: true
});

registry.register(googleDrive);
```

Supported adapter methods include:

```javascript
googleDrive.connect(options)
googleDrive.list(path)
googleDrive.search(path, query)
googleDrive.read(fileId)
googleDrive.write(path, fileName, data, options)
googleDrive.createFolder(path, folderName, options)
googleDrive.rename(fileId, newName)
googleDrive.remove(fileId)
googleDrive.copy(fileId, targetPath, options)
googleDrive.move(fileId, targetPath, options)
googleDrive.openFile({ multiple: true })
googleDrive.saveFile(fileName, data, { path: '/' })
```

`GoogleIdentityAuthService`, `GoogleDriveApiService`, and `GoogleDrivePickerService` are exported for Hosts that want to create and inject the services explicitly.

```javascript
var auth = new UnifiedFiler.services.GoogleIdentityAuthService({
    clientId: '<OAuth Client ID>',
    scopes: ['https://www.googleapis.com/auth/drive.file']
});
var api = new UnifiedFiler.services.GoogleDriveApiService({
    authService: auth,
    apiKey: '<API Key>'
});
var picker = new UnifiedFiler.services.GoogleDrivePickerService({
    authService: auth,
    apiKey: '<API Key>',
    appId: '<Google App ID>'
});
var googleDrive = new UnifiedFiler.adapters.GoogleDriveAdapter({
    authService: auth,
    apiService: api,
    pickerService: picker
});
```

## StorageRegistry

```javascript
registry.register(adapter)
registry.get(adapterId)
registry.list()
```

## FileService

```javascript
fileService.list(adapterId, path)
fileService.search(adapterId, path, query)
fileService.read(fileEntry)
fileService.write(adapterId, path, fileName, data, options)
fileService.rename(fileEntry, newName)
fileService.remove(fileEntry)
fileService.createFolder(adapterId, path, folderName)
fileService.mountStorage(adapterId, options)
fileService.openLocalFiles(adapterId, options)
fileService.saveLocalFile(adapterId, fileName, data, options)
fileService.uploadFiles(adapterId, path, files, options)
fileService.copy(sourceEntry, targetAdapterId, targetPath, options)
fileService.move(sourceEntry, targetAdapterId, targetPath, options)
```

## StorageAdapter 主要メソッド

```javascript
adapter.getId()
adapter.getLabel()
adapter.getCapabilities()
adapter.list(path)
adapter.search(path, query)
adapter.read(id)
adapter.write(path, fileName, data, options)
adapter.rename(id, newName)
adapter.remove(id)
adapter.createFolder(path, folderName, options)
adapter.openFile(options) // optional
adapter.saveFile(fileName, blob, options) // optional
adapter.mountDirectory(options) // optional
adapter.copy(id, targetPath, options) // optional
adapter.move(id, targetPath, options) // optional
```

## getCapabilities 例

```javascript
{
  canList: true,
  canRead: true,
  canWrite: true,
  canRename: true,
  canDelete: true,
  canCreateFolder: true,
  canSearch: true,
  canCopy: true,
  canMove: true,
  canDownload: true,
  canUpload: true,
  canExport: false,
  canShare: false,
  canPreview: true,
  canPickFiles: false,
  canPickDirectory: false,
  canMount: false,
  supportsProgress: false
}
```

## jQuery Plugins

```javascript
$('#fileExplorer').fileExplorer(options)
$('#fileExplorer').fileExplorer('getState')
$('#fileExplorer').fileExplorer('getSelectedEntry')
$('#fileExplorer').fileExplorer('openPath', 'appStorage', '/Documents')
$('#fileExplorer').fileExplorer('setStorage', 'appStorage')
$('#fileExplorer').fileExplorer('setViewMode', 'detail')
$('#fileExplorer').fileExplorer('setReadOnly', true)
$('#fileExplorer').fileExplorer('destroy')
$('#dialogHost').filePickerDialog(options)
$('#dialogHost').fileSaverDialog(options)
```

## FileExplorer options

```javascript
{
  defaultStorage: 'appStorage',
  storageTypes: ['appStorage', 'localDisk'],
  viewMode: 'detail', // detail / list / icon
  theme: 'windows11', // windows11 / default
  language: 'ja',
  enablePreview: true,
  enableMetadata: true,
  enableRecentFiles: true,
  enableThumbnails: true,
  thumbnailSize: 96,
  enableUpload: true,
  allowMultipleUpload: true,
  overwriteOnUpload: false,
  folderFirst: true,
  showTree: true,
  showStorageSelector: true,
  treeStorageMode: 'all', // all / active
  refreshTreeOnNavigation: false,
  treePaneMinWidth: 190,
  treePaneMaxWidth: 460,
  previewPaneMinWidth: 250,
  previewPaneMaxWidth: 620,
  detailColumns: ['name', 'updatedAt', 'type', 'size'],
  columnWidths: { name: 320, updatedAt: 220, type: 190, size: 120 },
  newMenuItems: [
    { id: 'folder', label: 'Folder', action: 'newFolder', iconClass: 'fa-folder' },
    { id: 'text', label: 'Text File', action: 'createFile', defaultName: 'untitled.txt', mimeType: 'text/plain', content: '' }
  ],
  readOnly: false,
  searchDelay: 220,
  enableLargePreviewGuard: true,
  maxPreviewSize: 1048576,
  enablePackagePreview: true,
  maxPackagePreviewSize: 20971520,
  packagePreviewMaxEntries: 60,
  allowedExtensions: [],
  allowedMimeTypes: [],
  showDisallowedFiles: true,
  onSelected: function (payload) {},
  onOpenFile: function (payload) {},
  onEvent: function (name, payload) {},
  services: {
    fileService: fileService,
    storageRegistry: storageRegistry,
    recentFileService: recentFileService,
    favoriteFileService: favoriteFileService,
    metadataService: metadataService,
    previewService: previewService,
    progressService: progressService
  },
  i18n: i18n
}
```


## FileExplorer events

FileExplorer emits jQuery events and option callbacks for Host integration.

```javascript
$('#fileExplorer').on('fileexplorer:selected', function (event, payload) {
  console.log(payload.entry);
});

$('#fileExplorer').fileExplorer({
  onEvent: function (name, payload) {
    console.log(name, payload);
  }
});
```

Representative event names:

```text
ready
refreshed
searched
selected
openFile
viewModeChanged
readOnlyChanged
folderCreated
fileCreated
uploaded
renamed
deleted
copied
moved
clipboardChanged
virtualListOpened
favoriteChanged
metadataSaved
mounted
progress
error
```

## FileExplorer keyboard shortcuts

```text
Enter      open selected folder / emit openFile for selected file
F2         rename selected entry
Delete     delete selected entry
Ctrl+C     copy selected entry
Ctrl+X     cut selected entry
Ctrl+V     paste into current folder
Ctrl+F     focus search box
```


## OperationProgressService v1.4

```javascript
var progressService = new OperationProgressService();

progressService.on(function (event) {
  console.log(event.eventName, event.operation.percent);
});

var operation = progressService.start({
  type: 'upload',
  label: 'Upload',
  total: file.size
});

progressService.update(operation.id, loaded, total, file.name);
progressService.finish(operation.id);
```

`FileService.uploadFiles()` and `LocalDiskAdapter.saveFile()` can use progress callbacks. Host systems can also reuse the service for PHP/Node/API-backed transfer adapters.

## LocalDiskAdapter v1.4

When the browser supports the File System Access API, `LocalDiskAdapter` can mount a user-selected directory. After mounting, FileExplorer can list, read, write, create folders, delete, and search inside that mounted scope. Unsupported browsers continue to use file-input/open and download/save fallback behavior.

```javascript
fileService.mountStorage('localDisk', { readOnly: false }).then(function () {
  return fileService.list('localDisk', '/');
});

fileService.saveLocalFile('localDisk', 'sample.txt', new Blob(['hello'], { type: 'text/plain' }));
```

## Sidecar services v1.3

```javascript
var favoriteFileService = new FavoriteFileService();
var metadataService = new MetadataService();
var previewService = new PreviewService();

favoriteFileService.list();
favoriteFileService.add(fileEntry);
favoriteFileService.remove(fileEntry);
favoriteFileService.toggle(fileEntry);

metadataService.get(fileEntry);
metadataService.save(fileEntry, { description: 'memo', tags: ['tag1'] });
metadataService.attachToEntries(entries);
metadataService.copy(sourceEntry, targetEntry);
metadataService.move(oldEntry, newEntry);
metadataService.remove(fileEntry);

previewService.render(fileService, fileEntry, options);
previewService.createThumbnail(fileService, fileEntry, { thumbnailSize: 96 });
```

Sidecar services are keyed by `adapterId + path`, so Host systems can replace the localStorage implementations with API-backed implementations later.

## FilePickerDialog options

```javascript
$('#dialogHost').filePickerDialog({
  title: 'Open File',
  defaultStorage: 'appStorage',
  defaultPath: '/',
  storageTypes: ['appStorage', 'localDisk'],
  selectionType: 'file', // file / folder / both
  multiple: false,
  allowedExtensions: ['.txt', '.md', '.json'],
  acceptMimeTypes: ['text/*', 'application/json'],
  showFolders: true,
  showFilteredFiles: true,
  showFolderTree: true,      // show a left folder tree pane
  treeStorageMode: 'active', // active / all
  expandTreeRoot: true,
  folderTreeWidth: 190,
  folderTreeMinWidth: 150,
  contentAreaMinHeight: 380,
  maximizeMainArea: true,
  compactToolbar: true,
  compactFooter: true,
  fitToViewport: true,
  viewportMargin: 32,
  openOnDoubleClick: true,
  closeOnSelect: true,
  services: {
    fileService: fileService,
    storageRegistry: storageRegistry,
    progressService: progressService
  },
  onSelect: function (files, state) {
    console.log(files, state);
  },
  onCancel: function (state) {},
  onError: function (error) {},
  onEvent: function (name, payload, instance) {}
});
```

`showTree` and `showFolderTree` are equivalent aliases. Keep them `false` for the compact list-only dialog, or set one of them to `true` when the Host wants Explorer-style folder navigation. `treeStorageMode: 'active'` shows only the selected storage in the tree, while `treeStorageMode: 'all'` shows all listable storages allowed by `storageTypes`. v1.9.4-fix1 makes the picker main browser area the primary flex region; `folderTreeWidth`, `folderTreeMinWidth`, `contentAreaMinHeight`, `maximizeMainArea`, `compactToolbar`, `compactFooter`, `fitToViewport`, and `viewportMargin` allow Host systems to tune the usable list/tree area without rewriting dialog CSS.

FilePicker result entries are normalized so the Host can handle all adapters with one structure.

```javascript
{
  id: 'entry-id',
  name: 'sample.txt',
  path: '/Documents/sample.txt',
  parentPath: '/Documents',
  adapterId: 'appStorage',
  storageType: 'appStorage',
  isFolder: false,
  size: 1234,
  mimeType: 'text/plain',
  updatedAt: Date,
  entry: originalStorageEntry,
  nativeFile: File // only when selected from LocalDisk browser input
}
```

FilePicker emits `filepicker:*` events. Representative names are:

```text
ready
changeStorage
changePath
selectionChange
select
cancel
error
```

## FileSaverDialog options

```javascript
$('#dialogHost').fileSaverDialog({
  title: 'Save As',
  defaultFileName: 'untitled.txt',
  defaultStorage: 'appStorage',
  defaultPath: '/',
  storageTypes: ['appStorage', 'localDisk'],
  overwriteMode: 'confirm', // confirm / rename / overwrite / error
  showFolderTree: true,      // show a left folder tree pane
  treeStorageMode: 'active', // active / all
  expandTreeRoot: true,
  folderTreeWidth: 190,
  folderTreeMinWidth: 150,
  contentAreaMinHeight: 380,
  maximizeMainArea: true,
  compactToolbar: true,
  compactFooter: true,
  fitToViewport: true,
  viewportMargin: 32,
  fileTypes: [
    { label: 'Text File', extension: '.txt', mimeType: 'text/plain' },
    { label: 'JSON File', extension: '.json', mimeType: 'application/json' }
  ],
  getData: function (request) {
    return 'content to save';
  },
  services: {
    fileService: fileService,
    storageRegistry: storageRegistry,
    progressService: progressService
  },
  onBeforeSave: function (request, instance) { return true; },
  onSave: function (request, savedEntry, instance) {},
  onSaved: function (savedEntry, request, instance) {},
  onCancel: function (state, instance) {},
  onError: function (error, instance) {},
  onEvent: function (name, payload, instance) {}
});
```

`showTree` and `showFolderTree` are equivalent aliases. When enabled, the saver dialog shows a folder tree for destination selection. `treeStorageMode: 'active'` limits the tree to the current writable storage, while `treeStorageMode: 'all'` shows all writable/listable storages allowed by `storageTypes`. v1.9.4-fix1 also applies a main-area-first layout to Save As: the toolbar, hint, footer, filename and type rows are compacted, and the tree/list browser area grows with the dialog height.

When `getData`, `dataProvider`, `data`, or `content` is supplied, the dialog writes to the selected adapter. If no data provider is supplied, the dialog runs in request-only mode and calls `onSave(request)` so the Host application can perform its own save workflow.

Save request objects are normalized as follows.

```javascript
{
  storageType: 'appStorage',
  adapterId: 'appStorage',
  path: '/Documents',
  fileName: 'untitled.txt',
  extension: '.txt',
  mimeType: 'text/plain',
  overwriteMode: 'confirm'
}
```

FileSaver emits `filesaver:*` events. Representative names are:

```text
ready
changeStorage
changePath
saveRequest
save
saved
cancel
error
```


## Backend API support v1.5

### BackendApiAdapter

`BackendApiAdapter` is a storage adapter for backend-managed files. It exposes the same adapter contract as `IndexedDbAdapter` and `LocalDiskAdapter`, so FileExplorer, FilePickerDialog and FileSaverDialog do not need backend-specific UI code.

```javascript
var backendApi = new BackendApiAdapter({
    id: 'backendApi',
    label: 'Backend API',
    baseUrl: '/api',
    endpointRoot: '/filer',
    tenantId: activeTenantId,
    appId: 'UnifiedFiler',
    userId: currentUserId
});
registry.register(backendApi);
```

The adapter also accepts a direct `backend` object for tests, local development, or Host-provided service bridges. The object may implement `list`, `search`, `read`, `write`, `createFolder`, `rename`, `remove`, `copy`, and `move`.

### ApiClientService

`ApiClientService` centralizes backend calls. It supports `baseUrl`, default headers, credentials, timeout, custom transport, and context headers:

- `X-Tenant-Id`
- `X-App-Id`
- `X-User-Id`

### Suggested backend endpoints

```text
GET    /filer/entries?path=/Documents
GET    /filer/search?path=/&q=readme
GET    /filer/entries/{id}/content
POST   /filer/entries/upload
POST   /filer/folders
PATCH  /filer/entries/{id}
DELETE /filer/entries/{id}
POST   /filer/entries/{id}/copy
POST   /filer/entries/{id}/move
```

Response entries should use this shape where possible:

```json
{
  "id": "file_001",
  "name": "readme.txt",
  "path": "/Documents/readme.txt",
  "parentPath": "/Documents",
  "isFolder": false,
  "mimeType": "text/plain",
  "size": 120,
  "createdAt": "2026-06-02T00:00:00.000Z",
  "updatedAt": "2026-06-02T00:00:00.000Z"
}
```

### MemoryApiBackendService

`MemoryApiBackendService` is included for demos and smoke tests. It emulates a backend service in the browser and persists small sample data to `localStorage`. Production Hosts should replace it with a real PHP or NodeJS backend.

## v1.9.4-fix3 Splitter Options

FileExplorer side panes are flex children. From v1.9.4-fix3 the left tree pane and right preview pane use a custom splitter controller instead of jQuery UI `resizable()`. This avoids reversed movement when dragging the preview splitter in a flex row.

```javascript
{
  treePaneMinWidth: 190,
  treePaneMaxWidth: 460,
  previewPaneMinWidth: 250,
  previewPaneMaxWidth: 620
}
```

The preview splitter follows Windows Explorer style direction: dragging left widens the preview pane and dragging right narrows it. The tree splitter uses the opposite side naturally: dragging right widens the tree pane and dragging left narrows it.

## v1.8.4 Interaction Options

### `enableMultipleSelection`

Type: `boolean`  
Default: `true`

Enables Explorer-style multi-selection in the file list.

Supported operations:

- Ctrl/Cmd + click: toggle one entry
- Shift + click: range select
- Ctrl/Cmd + A: select all visible entries
- Batch copy / cut / paste / delete / drag and drop

### `enableKeyboardShortcuts`

Type: `boolean`  
Default: `true`

Enables keyboard operations while the FileExplorer root has focus.

| Key | Action |
| --- | --- |
| Enter | Open selected file/folder |
| F2 | Rename selected item |
| Delete | Delete selected item(s) |
| Esc | Clear selection / cancel pending rename |
| Up / Down | Move selection |
| Home / End | Jump to first / last entry |
| Shift + navigation | Extend selection range |
| Ctrl/Cmd + A | Select all |
| Ctrl/Cmd + C | Copy selection |
| Ctrl/Cmd + X | Cut selection |
| Ctrl/Cmd + V | Paste clipboard |
| Ctrl/Cmd + F | Focus search |

### Public API: `getSelectedEntries()`

Returns a copy of the current selected entries array.

```javascript
var selectedEntries = $('#fileExplorer').fileExplorer('getSelectedEntries');
```


## Inline rename options

```javascript
{
  enableInlineRename: true,
  inlineRenameOnNameClick: true,
  inlineRenameDelay: 420,
  inlineRenameTrigger: 'selectedNameClick', // selectedNameClick | nameClick | manual
  inlineRenameSelectNameWithoutExtension: true,
  inlineRenameMinWidth: 96,
  inlineRenameMaxWidth: 520
}
```

- `selectedNameClick`: Windows-like default. The first click selects the item; a later click on the already-selected visible name starts inline rename.
- `nameClick`: starts inline rename after a short delay when a visible file/folder name is clicked.
- `manual`: disables mouse-triggered rename; F2, toolbar Rename, context-menu Rename and `startInlineRename()` remain available.
- `inlineRenameSelectNameWithoutExtension`: when true, file rename selects only the base name while the extension remains visible, matching Windows Explorer behavior.
- `inlineRenameMinWidth` / `inlineRenameMaxWidth`: control the calculated rename textbox width in detail, list, and icon views.

## v1.9 Preview API

### PreviewService.renderData(data, options)

Renders a Blob/string/ArrayBuffer value without requiring a FileEntry.

```javascript
previewService.renderData(blob, {
  name: 'sample.md',
  mimeType: 'text/markdown'
});
```

Supported preview types include Markdown, JSON, PDF, image, sandboxed HTML and plain text fallback.

## v1.9 Google Drive API additions

### GoogleDriveAdapter.exportFile(id, mimeType, options)

Exports a Google Workspace native file to a downloadable Blob.

### GoogleDriveAdapter.listSharedDrives(options)

Lists Shared Drives visible to the authenticated user.

### Google Drive write options

```javascript
fileService.write('googleDrive', '/Documents', 'sample.txt', blob, {
  conflictMode: 'rename',      // rename / overwrite / skip / error
  uploadStrategy: 'auto',      // multipart / resumable / auto
  resumableThreshold: 8388608
});
```

## v1.9 FileService batch APIs

```javascript
fileService.copyEntries(entries, 'appStorage', '/Archive', { conflictMode: 'rename' });
fileService.moveEntries(entries, 'googleDrive', '/Archive', { conflictMode: 'overwrite' });
fileService.removeEntries(entries);
fileService.exportFile(entry, 'application/pdf');
```

## v1.9 HostIntegrationService

```javascript
var integration = new UnifiedFiler.services.HostIntegrationService({ host: hostSystem });
integration.registerAssociation('.umal', {
  open: function (entry) {
    return hostSystem.launch('OperationManual', { fileEntry: entry });
  }
});
```


## FileExplorer tree refresh option

`refreshTreeOnNavigation` controls whether normal folder navigation rebuilds the left tree.

```javascript
$('#host').fileExplorer({
  refreshTreeOnNavigation: false
});
```

The default is `false`. This is important for Google Drive and other cloud adapters because a full tree rebuild can re-query every expanded branch and reset the visible tree position. Explicit refresh and structural operations may still rebuild the tree when necessary.


## Preview safety and Blob URL lifecycle

`enableLargePreviewGuard` is enabled by default. When `entry.size` is available and exceeds `maxPreviewSize`, FileExplorer shows a size warning before reading the file. Package previews use `maxPackagePreviewSize` separately because ZIP/native packages can be much larger than ordinary text/image previews.

Image, PDF, and sandboxed HTML previews create browser object URLs. FileExplorer now owns those URLs and revokes them when the selection changes, the preview is refreshed, or the component is destroyed. Host code using `PreviewService` directly can call `renderWithResources()` to get both the HTML and the object URLs it must revoke.

```javascript
previewService.renderWithResources(fileService, entry, {
  maxPreviewSize: 1024 * 1024
}).then(function (result) {
  previewPane.innerHTML = result.html;
  // Store result.objectUrls and revoke them when replacing this preview.
});
```

## Incremental tree update

With `refreshTreeOnNavigation: false`, normal navigation keeps the tree DOM stable. v1.9.2 also updates expand/collapse and common structural operations branch-by-branch where possible, so Google Drive trees keep their scroll position and expanded siblings during routine use. Explicit Refresh still performs a full tree rebuild. v1.9.3 applies the same branch-based tree interaction concept to optional FilePickerDialog and FileSaverDialog folder trees.


## v1.9.6 Command API

`FileExplorerControl` now exposes command-centered APIs for Host integration.

```javascript
control.exec('rename');
control.canExec('delete');
control.getCommandState('rename');
control.getCommandStateMap();
control.getCommandsForSurface('context', { context: contextTarget });
control.resolveShortcut(event);
```

Command lifecycle events are emitted from the Control.

```javascript
control.on('beforeCommand', function (event) {});
control.on('afterCommand', function (event) {});
control.on('commandError', function (event) {});
control.on('commandStateChanged', function (event) {});
```

The internal View/Controller can still be used as a jQuery plugin, but when owned by `FileExplorerControl`, toolbar buttons, context-menu items, and keyboard shortcuts route through the Control command registry before reaching the View action implementation.

### Command state

Command state is calculated from:

- active storage capabilities
- current selection
- clipboard state
- history state
- preview state
- context-menu target information
- read-only option

Each command state includes:

```javascript
{
  id: 'rename',
  labelKey: 'rename',
  iconClass: 'fa fa-pencil',
  shortcut: 'F2',
  group: 'edit',
  order: 330,
  toolbar: true,
  context: true,
  visible: true,
  enabled: true,
  checked: false
}
```


## v1.9.6 Internal View Component API

The Host-facing API remains the Control layer. Internally, `FileExplorerView` now composes smaller jQuery View/Controller modules:

```text
FileExplorerView
  ├─ FileToolbarView
  ├─ FileTreeView
  ├─ FileItemsView
  ├─ FilePreviewPaneView
  ├─ FileMetadataPaneView
  └─ FileContextMenuView
```

These classes are exported through `UnifiedFiler.views` for diagnostics and advanced embedding, but normal Host integrations should use `FileExplorerControl`, `FilePickerControl`, and `FileSaverControl`.

`FileExplorerControl` passes `controlStateStore` and `controlEventBus` to the internal view layer. View updates can call `_publishState(reason, patch)` through the owning `FileExplorerView` to keep the Control state synchronized without exposing View internals to Host code.


## v1.9.7 Google Drive UI options

```javascript
{
  enableSharedWithMeNode: true,
  enableSharedDrivesNode: true,
  uploadConflictMode: 'confirm',
  googleDriveConflictMode: 'confirm',
  googleDriveUploadStrategy: 'auto',
  showOperationProgressPanel: true
}
```

The Explorer now recognizes Google Drive virtual paths such as `/Shared with me` and `/Shared Drives`. Google Workspace native files can be exported through the `exportWorkspace` command, and files with `webViewLink` can be opened with `openWeb`.


## Google Drive permissions and per-file capabilities

Google Drive adapter-level support does not mean every Drive file can be renamed, deleted, moved, or downloaded. UnifiedFiler merges adapter capabilities with the selected entry's Drive `capabilities` metadata before enabling commands.

For full browser file-manager operations over arbitrary Drive files, configure the Host with:

```javascript
googleDrive: {
    scopes: ['https://www.googleapis.com/auth/drive']
}
```

For a narrower security model, `https://www.googleapis.com/auth/drive.file` can still be used, but files should be selected through Google Picker or created by the app before write operations such as rename/update are attempted.


## GoogleIdentityAuthService single-flight token flow

From v1.9.7-fix2, `GoogleIdentityAuthService` serializes concurrent token requests. When multiple Drive API calls request a token at nearly the same time, the first call owns the GIS `requestAccessToken()` popup flow and the remaining calls reuse the same pending Promise.

Relevant behavior:

```javascript
var auth = new UnifiedFiler.services.GoogleIdentityAuthService({
    clientId: '<OAuth Client ID>',
    scopes: ['https://www.googleapis.com/auth/drive'],
    prompt: ''
});

// Explicit user action can still force the consent UI.
auth.authorize({ prompt: 'consent' });
```

Google Drive upload is browser-only. Host-side COOP settings are deployment/environment concerns only; UnifiedFiler does not require server-side code or a backend proxy for Google Drive upload.

## v1.9.8 FilePicker / FileSaver Control API

### FilePickerControl

```javascript
var picker = new UnifiedFiler.controls.FilePickerControl({
  services: services,
  hostProfile: 'viewer',
  enablePreview: true,
  enableRecentFiles: true,
  enableFavorites: true
});

picker.open().then(function (result) {
  // result.entries / result.files
});
```

Important methods:

- `open(options)`
- `openPath(path)` / `openFolder(path)`
- `refresh()`
- `select()`
- `setStorage(adapterId)`
- `getSelection()`
- `getState()`
- `on(name, handler)` / `off(name, handler)`

Preview-related options:

```javascript
enablePreview: true,
previewPaneWidth: 300,
maxPreviewSize: 2 * 1024 * 1024
```

Recent / Favorite options:

```javascript
enableRecentFiles: true,
enableFavorites: true
```

These options require `recentFileService`, `favoriteFileService`, and `previewService` in the `services` object when the corresponding feature is used.

### FileSaverControl

```javascript
var saver = new UnifiedFiler.controls.FileSaverControl({
  services: services,
  hostProfile: 'office',
  workflow: 'saveAs',
  defaultFileName: 'document.txt'
});

saver.save('content', { fileName: 'document.txt' }).then(function (result) {
  // result.request / result.savedEntry
});
```

Important methods:

- `open(options)`
- `save(data, options)`
- `getSaveRequest()`
- `setFileName(fileName)`
- `setData(data)`
- `setWorkflow(workflow)`
- `openPath(path)` / `openFolder(path)`
- `refresh()`
- `setStorage(adapterId)`
- `getState()`

The normalized save request includes:

```javascript
{
  adapterId: 'appStorage',
  path: '/',
  fileName: 'document.txt',
  mimeType: 'text/plain',
  overwriteMode: 'confirm',
  workflow: 'saveAs',       // saveAs / export / packageSave
  exportFormat: '',
  packageType: '',
  sourceEntry: null,
  hostProfile: 'office'
}
```

### HostProfilePresetService

```javascript
var presets = new UnifiedFiler.services.HostProfilePresetService();
var options = presets.apply({ hostProfile: 'editor' }, 'picker');
```

Built-in profiles:

- `default`
- `unifiedDesktop`
- `office`
- `viewer`
- `editor`
- `package`

Explicit Host options always override preset values.


## v1.9.8-fix1 This Device attach workflow for Picker/Saver

`FilePickerDialog` and `FileSaverDialog` now use the same LocalDisk model as `FileExplorer`. For `This Device`, the dialog displays a mount card first. After the user grants folder access through the browser File System Access API, the mounted folder is listed and used for selection or saving.

Relevant options:

```javascript
{
  defaultStorage: 'localDisk',
  showFolderTree: true,
  localFileFallback: false, // FilePicker: opt-in one-off file picker fallback
  localSaveFallback: false  // FileSaver: opt-in one-off save picker fallback
}
```

Control helpers:

```javascript
picker.mountStorage('localDisk');
saver.mountStorage('localDisk');
```

`LocalDiskAdapter` also validates File System Access API accept metadata, so MIME patterns such as `text/*` are grouped as MIME accept keys rather than invalid extension values.


## v1.9.9 Backend API Contract

`UnifiedFiler.services.BackendApiContractService` describes the stable v1.0 backend contract.

```javascript
var contract = new UnifiedFiler.services.BackendApiContractService({
    endpointRoot: '/filer'
});
console.log(contract.describe());
```

`BackendApiAdapter` now exposes:

```javascript
adapter.getBackendSpec();
adapter.getApiContract();
adapter.metadata(id);
adapter.updateMetadata(id, metadata);
adapter.thumbnail(id);
```

Supported Host context fields are `tenantId`, `appId`, `userId`, `workspaceId`, `ownerObjectType`, `ownerObjectId`, `permissionScope`, `requestId` and `contractVersion`.

## v1.9.9 Security Policy

`UnifiedFiler.services.SecurityPolicyService` validates file names, paths, upload batches, MIME/extension rules and package-preview safety limits.

```javascript
var policy = new UnifiedFiler.services.SecurityPolicyService({
    maxUploadFileSize: 512 * 1024 * 1024,
    blockedExtensions: ['.exe', '.bat', '.cmd', '.msi']
});

var fileService = new UnifiedFiler.services.FileService({
    registry: storageRegistry,
    securityPolicy: policy
});
```

`FileService` also exposes metadata helpers:

```javascript
fileService.metadata(entry);
fileService.updateMetadata(entry, { tags: ['important'] });
fileService.thumbnail(entry);
```


## v1.9.9-fix1 Breadcrumb display option

`FileExplorerView` / `FileExplorerControl` の Breadcrumb は、既定で Storage / Driver 名を表示しません。
Storage は Storage Selector で明示されるため、Breadcrumb は現在のフォルダ階層だけを表示します。

```javascript
$('#explorer').fileExplorer({
    defaultStorage: 'googleDrive',
    showStorageInBreadcrumb: false // default
});
```

従来のように Breadcrumb 先頭へ Storage 名を表示したいHostは以下を指定できます。

```javascript
$('#explorer').fileExplorer({
    showStorageInBreadcrumb: true
});
```


## Create Type Extension API - v1.9.9-fix2

`FileExplorerControl` owns a `CreateTypeRegistry` used by the FileExplorer New menu. Host systems can register custom file or folder types without directly modifying the internal jQuery View.

```javascript
var control = new UnifiedFiler.controls.FileExplorerControl('#explorer', {
    services: services,
    createTypes: [
        {
            id: 'dokuwiki-page',
            kind: 'file',
            label: 'DokuWiki Page',
            defaultName: 'new-page.txt',
            mimeType: 'text/plain',
            content: '====== New Page ======\n\n'
        }
    ]
});
control.mount();

control.registerCreateType({
    id: 'unified-model',
    kind: 'file',
    label: 'Unified Model',
    defaultName: 'new-model.umdlx',
    mimeType: 'application/vnd.itoolkits.umdlx',
    create: function (context) {
        return {
            name: context.name,
            mimeType: 'application/vnd.itoolkits.umdlx',
            content: JSON.stringify({ manifest: { type: 'umdlx', version: '1.0' } }, null, 2)
        };
    }
});

control.exec('createItem', { typeId: 'unified-model' });
```

### FileExplorerControl create APIs

- `registerCreateType(type)`
- `registerCreateTypes(types)`
- `unregisterCreateType(typeId)`
- `getCreateType(typeId)`
- `getCreateTypes(context)`
- `createItem(typeId, options)`
- `exec('createItem', { typeId: '...' })`

### Create type fields

- `id`: stable type id.
- `kind`: `file` or `folder`.
- `label`: display label in the New menu.
- `iconClass`: optional Font Awesome class without requiring Host UI changes.
- `defaultName`: name shown in the creation prompt.
- `mimeType`: MIME type for files.
- `content`: static content or `function(context)` returning content.
- `create(context)`: advanced factory returning a descriptor or Promise.
- `canCreate(context)`: optional permission hook.
- `visibleWhen(context)`: optional New menu visibility hook.
- `order`: New menu display order.

The legacy `newMenuItems` option remains supported for compatibility, but new Host integrations should use the Control APIs.


## Preview Provider Extension API - v1.9.9-fix3

`PreviewProviderRegistry` is the Host-facing registry for custom preview renderers. It complements `CreateTypeRegistry`: create types control what can be created, while preview providers control how those custom file types are displayed in Explorer and Picker preview panes.

### Registering a provider

```javascript
control.registerPreviewProvider({
    id: 'dokuwiki-preview',
    label: 'DokuWiki Preview',
    extensions: ['.dokutxt', '.wiki'],
    mimeTypes: ['text/x-dokuwiki'],
    priority: 100,
    trustedHtml: true,
    render: function (context) {
        return context.read().then(function (result) {
            return result.data.text();
        }).then(function (text) {
            return {
                html: '<div class="dokuwiki-preview">' + context.escape(text) + '</div>',
                trustedHtml: true
            };
        });
    }
});
```

### Provider fields

| Field | Purpose |
|---|---|
| `id` | Required unique provider id. |
| `label` | Human-readable label for diagnostics or future UI. |
| `extensions` | Extension match list such as `['.umdlx', '.ualbx']`. |
| `mimeTypes` | MIME match list. Wildcards like `text/*` are supported. |
| `match(entry, context)` | Optional custom matcher. |
| `canPreview(entry, context)` | Optional capability check. Return `false` to skip. |
| `priority` | Higher priority wins. |
| `maxSize` | Optional provider-specific preview size limit. |
| `trustedHtml` | If true, returned HTML is inserted as HTML. Otherwise it is escaped. |
| `render(context)` | Required renderer. Returns string, `{ html }`, `{ text }`, `{ blob }`, or a Promise. |

### Render context

```javascript
{
    fileService: fileService,
    entry: entry,
    options: options,
    escape: function (text) {},
    read: function () {},
    createObjectUrl: function (blob) {},
    renderStandard: function () {}
}
```

`createObjectUrl(blob)` registers the object URL with the Preview lifecycle so UnifiedFiler can revoke it automatically when the selection changes.

### Control APIs

```javascript
control.registerPreviewProvider(provider);
control.registerPreviewProviders([provider]);
control.unregisterPreviewProvider('provider-id');
control.getPreviewProvider('provider-id');
control.getPreviewProviders();
```

`FilePickerControl` also supports `registerPreviewProvider()` and `registerPreviewProviders()` when `enablePreview: true` is used.

---

## Product v1.0 Stable Public API

Product v1.0.0 fixes the Host-facing API as a stable contract. For new integrations, prefer Control APIs over direct manipulation of internal Views.

```javascript
require(['UnifiedFiler/main'], function (UnifiedFiler) {
    var filer = UnifiedFiler.create({ language: 'ja' });
    var explorer = filer.createExplorer('#host', { defaultStorage: 'appStorage' });

    explorer.exec('refresh');
    explorer.registerCreateType({
        id: 'host-note',
        kind: 'file',
        label: 'Host Note',
        defaultName: 'note.txt',
        content: ''
    });
    explorer.registerPreviewProvider({
        id: 'host-note-preview',
        extensions: ['.txt'],
        priority: 50,
        render: function (context) {
            return context.read().then(function (result) {
                return result.data.text();
            }).then(function (text) {
                return { html: '<pre>' + context.escape(text) + '</pre>' };
            });
        }
    });
});
```

See `docs/PUBLIC_API_CONTRACT.md` for the stable Product v1.0 method list.

### Contract inspection

```javascript
var contract = UnifiedFiler.getPublicApiContract();
console.log(contract.productVersion); // 1.0.0
```

### Lightweight regression tests

```javascript
UnifiedFiler.runRegressionTests().then(function (summary) {
    console.log(summary.passed, summary.results);
});
```

The browser demo is available at `demo/regression-tests.html`.


## Product v1.0.0 included additions from dev-2.1.0

- Extended metadata editing UI.
- PermissionPolicyService and capability badges.
- FileSaver workflow/export-format UI.
- HostApplicationBridgeService for UnifiedDesktop / Viewer / Editor / Owners Reform / Sappane Tools.
- GoogleDriveWorkflowService descriptors.
- PHP / Node.js backend API samples.
