Skip to content

Conversation

@pdesoyres-cc
Copy link
Contributor

Summary

  • Add cc-cellar-object-list component to browse and manage objects (files/directories) within a Cellar bucket
  • Add cc-breadcrumbs component for hierarchical path navigation
  • Add enableCopyToClipboard option to cc-grid link cells
  • Extract Abortable utility class to a generic lib for request cancellation
  • Integrate object list navigation into cc-cellar-explorer

Details

New components

cc-cellar-object-list

A full-featured component for browsing Cellar bucket contents with:

  • Grid-based display of files and directories with type-specific icons (images, videos, audio, archives, PDFs, etc.)
  • Breadcrumb navigation for directory traversal
  • Filtering by object name prefix
  • Pagination support (previous/next)
  • Object actions: download, delete (with drawer)

cc-breadcrumbs

A reusable breadcrumb navigation component that:

  • Displays a hierarchical path of items
  • Supports icons and custom labels
  • Emits click events with the path to the selected item

Enhancements

  • cc-grid: Link cells can now display a copy-to-clipboard button via enableCopyToClipboard: true
  • cc-cellar-explorer: Uses Abortable for smart request cancellation when navigating between buckets/paths

Refactoring

  • Moved Abortable class from kv-utils.js to src/lib/abortable.js for reuse across components

How to review

  • Verify cc-breadcrumbs stories render correctly and click events work
  • Verify cc-grid stories
  • Verify copy-to-clipboard button on grid link cells
  • Verify cc-cellar-object-list stories
  • Use sandbox or staging-five
    • Test navigation between directories using breadcrumbs
    • Test object filtering by prefix
    • Test pagination (previous/next page)
    • Test download and delete actions
    • Monkey test rapid switching buckets/paths using breadcrumb (you should never land with an inconsistent object lists)

@github-actions
Copy link
Contributor

🔎 A preview has been automatically published : https://clever-components-preview.cellar-c2.services.clever-cloud.com/cellar-explorer-objects-1/index.html.

This preview will be deleted once this PR is closed.

@pdesoyres-cc pdesoyres-cc force-pushed the cellar-explorer-objects-1 branch from 2c736be to 041c90d Compare January 27, 2026 16:41
@pdesoyres-cc pdesoyres-cc marked this pull request as draft January 27, 2026 16:48
@pdesoyres-cc pdesoyres-cc force-pushed the cellar-explorer-objects-1 branch from 041c90d to d200f04 Compare January 27, 2026 17:18
@pdesoyres-cc pdesoyres-cc marked this pull request as ready for review January 27, 2026 17:23
@hsablonniere hsablonniere changed the title feat(cellar-explorer): add objects managment feat(cellar-explorer): add objects management Feb 2, 2026
Copy link
Contributor

@HeleneAmouzou HeleneAmouzou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GG 👏 I have only left small feedbacks/questions, but otherwise LGTM !

];

/**
* A component that allows to navigate through a Cellar addon.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* A component that allows to navigate through a Cellar addon.
* A component that allows to navigate through a Cellar addon's bucket.

],
});

export const fetchingObject = makeStory(conf, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question : I don't visually see the fetching state, is this story broken or am I missing something ? 🤔

@HeleneAmouzou
Copy link
Contributor

I forgot to test on staging-five 🙈
Everything seems to work perfectly (navigation using breadcrumbs, pagination, filtering). A reflection about the actions ; when clickin the the Download button, I was expecting it to directly download the object, but instead it opens it in a new tab, is this the expected behavior ?

Copy link
Contributor

@florian-sanders-cc florian-sanders-cc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job @pdesoyres-cc

I did not find big issues, the component works great and looks great as well!

I faced an issue with the pagination:

  • The "next page" is available,
  • I click it,
  • It loads and I get empty states as if there were no objects 🤔
    (the bucket I'm testing contains all my notes, it contains 430 objects)

I wonder if the pagination may confuse people: icons only with left / right arrow could also mean go back / go forward like in browsers. Like I went from a bucket to its objects, I'm looking for a "back" icon to go back to the bucket list but that's not what the left arrow button does. I think I'm exaggerating, we're fine and we can just see if we get feedback about this subject from actual users.

We need to discuss how to handle focus when navigation happens (from bucket to object, from a object level to another). Right now the focus is lost most of the time, which makes the component not keyboard friendly. I did not think this through but I think when a navigation occurs, we should move focus to the first column + first row in the grid.
Also, when deleting objects, while filtered, the focus is lost after deleting the last object filtered. It should be placed on the empty message instead but it seems that it doesn't work? 🤔

* Dispatched when a breadcrumb item is clicked.
* @extends {CcEvent<{path: Array<string>}>}
*/
export class CcBreadcrumbClickEvent extends CcEvent {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: tough one but I wonder if removing the "s" here is a good idea, it makes it very easy to get the event name wrong since the component name contains a "s" (but I do understand why it's singlar here 🤷)

Or we could use a totally different event name like cc-navigation-request or something like that 🤔

}

ul {
align-items: end;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: this breaks alignment for breadcrumbs with no icons.

Image

To fix your issue, you can:

  1. set display: inline-flex; on cc-link (this will remove the blank space below the icon that led you to choose align-items: end in the first place),
  2. remove this align-items (or replace end with center but I don't think you need it anyway 🤔)
Image Image

>${cell.value}</cc-button
>`,
template: html`<div class="icon-label">
${cell.icon != null ? html`<cc-icon .icon=${cell.icon} ?skeleton=${skeleton}></cc-icon>` : ''}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: are we sure we'll always have decorative icons or should we expose & use a cell.iconA11yName?

* @param {function} orElse
* @param {boolean} [deleteMode]
*/
#handleErrorOnObject(error, objectKey, orElse, deleteMode = false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: 4 positioned args feels a lot to me, especially with a boolean at the end, it makes the signature harder to read & manipulate

When I read this for instance:

this.#handleErrorOnObject(
        error,
        objectKey,
        () => notifyError(i18n('cc-cellar-object-list.error.object-deletion-failed', { objectKey })),
        true,
      );

I have to check the actual function to understand what true means, what the callback is for.
It's a nitpick because when coding you have the LSP, which mitigates the issue but I feel like object + destructuring would be better.

this.dispatchEvent(new CcCellarNavigateToBucketEvent(this.state.bucketName));
}
this.dispatchEvent(new CcCellarNavigateToPathEvent(path.slice(2)));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question/suggestion: I bet this doesn't actually trigger an issue but it feels weird that we don't have any early returns in there. If path.length === 1 || path.length === 2, then we're dispatching both CcCellarNavigateToHomeEvent and CcCellarNavigateToPathEvent 🤔


return html`
<div class="path-wrapper">
<cc-breadcrumbs .items=${items} @cc-breadcrumb-click=${this._onPathItemClick}></cc-breadcrumbs>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: the breadcrumb should be placed in a <nav arial-label="{some translation for breadcrumb / fil d'Ariane}"
Should the component itself wrap the list of links in <nav>? I think so 🤔 (I'm not even sure Shadow DOM surfaces region landmarks correctly but we should use a <nav> element anyway).


<div class="details-sub-title">${i18n('cc-cellar-object-list.details.actions.title')}</div>
<div class="details-actions">
<cc-button
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: this should be a link unless we really don't have a choice. The current behavior does not actually trigger a download + opens a new window which is not the expected behavior.
We can discuss it in sync but I think we should stick with the way we handled this for Kube:

const kubeConfigFetchInterval = setInterval(() => {

It might be too complicated to do that in your case but it's worth trying because it would allow to provide the filename + extension so that it actually downloads the file when the user clicks (right now the user has to do all of this manually).

${object != null
? html`<div class="details-wrapper">
<div class="details-icon-wrapper">
<cc-icon .icon=${this._getFileIcon(object.contentType)}></cc-icon>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: the icon provides information about the type of file / media so we should provide an a11yName with this information

Copy link
Contributor

@florian-sanders-cc florian-sanders-cc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bunch of feedback from Claude (I forgot to run it but figured it could catch stuff I didn't)

export class CcBreadcrumbs extends LitElement {
static get properties() {
return {
disabled: { type: Boolean },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: this property is not implemented

'cc-cellar-explorer.error': `Une erreur est survenue pendant le chargement`,
//#endregion
//#region cc-cellar-object-list
'cc-cellar-object-list.back-to-bucket-list': `Retour à la list des buckets`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix:

Suggested change
'cc-cellar-object-list.back-to-bucket-list': `Retour à la list des buckets`,
'cc-cellar-object-list.back-to-bucket-list': `Retour à la liste des buckets`,

Comment on lines +572 to +573
white-space: wrap;
word-wrap: anywhere;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick(not sure): Claude tells me this, I'm not sure, I'll go and dig when I have time because I don't know enough about these 🙈

    white-space: wrap;  // Invalid - should be 'normal'                                                                                                                                
    word-wrap: anywhere;  // This is also deprecated, prefer overflow-wrap: anywhere                                                                                                   

try {
const signedUrl = await this.#cellarClient.getObjectSignedUrl(this.#bucketName, objectKey);

const element = document.createElement('a');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: if we really want to go down that road, we should use the download attribute on the link so that it has the proper behavior.
Still I think it's a smell that we might be doing this wrong: if what we need is the link behavior, we should use a link from the start because it will also bring the relevant semantics.

Comment on lines +110 to +111
/** @type {Ref<CcBreadcrumbs>} */
this._breadcrumbsRef = createRef();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: the breadcrumbsRef is not initialized on the element

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants